[chronojump-server] MAJOR COMMIT - Chronojump Networks now runs under Django 1.11.1
- From: Marcos Venteo Garcia <mventeo src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [chronojump-server] MAJOR COMMIT - Chronojump Networks now runs under Django 1.11.1
- Date: Mon, 2 Apr 2018 20:04:30 +0000 (UTC)
commit edea1ea23fd3eb0baff84c77679404457fff5d0b
Author: Marcos Venteo <mventeo gmail com>
Date: Mon Apr 2 22:04:18 2018 +0200
MAJOR COMMIT - Chronojump Networks now runs under Django 1.11.1
- Adding Docker support
.gitignore | 1 -
chronojumpserver-django/babel.cfg | 3 +
.../chronojump_networks/core/models.py | 42 ++
.../chronojump_networks/organizations/admin.py | 108 ++++
.../organizations/api/__init__.py | 1 +
.../organizations/api/permissions.py | 9 +
.../organizations/api/serializers.py | 35 ++
.../chronojump_networks/organizations/api/urls.py | 45 ++
.../chronojump_networks/organizations/api/views.py | 112 ++++
.../chronojump_networks/organizations/apps.py | 9 +
.../organizations/decorators.py | 21 +
.../chronojump_networks/organizations/forms.py | 29 +
.../organizations/migrations/0001_initial.py | 191 ++++++
.../migrations/0002_auto_20180331_1224.py | 25 +
.../migrations/0003_auto_20180331_1259.py | 22 +
.../organizations/migrations/0004_group_gym.py | 21 +
.../migrations/0005_auto_20180401_2146.py | 21 +
.../migrations/0006_auto_20180402_1751.py | 27 +
.../chronojump_networks/organizations/models.py | 242 ++++++++
.../chronojump_networks/organizations/tests.py | 6 +
.../chronojump_networks/organizations/urls.py | 51 ++
.../chronojump_networks/organizations/views.py | 93 +++
.../chronojump_networks/results/admin.py | 6 +
.../chronojump_networks/results/apps.py | 8 +
.../chronojump_networks/results/models.py | 6 +
.../chronojump_networks/results/tests.py | 6 +
.../chronojump_networks/results/views.py | 6 +
.../static/css/styles-airport.css | 82 +++
.../chronojump_networks/static/css/styles.css | 132 +++++
.../static/images/chronojump-logo.png | Bin 0 -> 18840 bytes
.../static/images/details_close.png | Bin 0 -> 686 bytes
.../static/images/details_open.png | Bin 0 -> 709 bytes
.../chronojump_networks/static/js/chronojump.js | 40 ++
.../chronojump_networks/static/js/tasks.js | 1 +
.../chronojump_networks/tasks/admin.py | 11 +
.../chronojump_networks/tasks/api/serializers.py | 23 +
.../chronojump_networks/tasks/api/urls.py | 19 +
.../chronojump_networks/tasks/api/views.py | 52 ++
.../chronojump_networks/tasks/apps.py | 8 +
.../tasks/migrations/0001_initial.py | 30 +
.../tasks/migrations/0002_auto_20180401_2209.py | 89 +++
.../tasks/migrations/0003_task_load.py | 20 +
.../chronojump_networks/tasks/models.py | 67 +++
.../chronojump_networks/tasks/tests.py | 6 +
.../chronojump_networks/tasks/views.py | 6 +
.../templates/admin/base_site.html | 9 +
.../chronojump_networks/templates/base.html | 39 ++
.../chronojump_networks/templates/layout.html | 62 ++
.../organizations/groups/group_players_list.html | 603 ++++++++++++++++++++
.../organizations/groups/task_modal_form.html | 254 ++++++++
.../templates/organizations/gyms/gym_detail.html | 193 +++++++
.../organizations/organization_detail.html | 25 +
.../templates/organizations/organization_list.html | 17 +
.../templates/pages/403_csrf.html | 9 +
.../chronojump_networks/templates/pages/404.html | 9 +
.../chronojump_networks/templates/pages/500.html | 13 +
.../chronojump_networks/templates/pages/index.html | 121 ++++
.../chronojump_networks/templates/users/login.html | 31 +
chronojumpserver-django/config/settings/base.py | 239 ++++++++
chronojumpserver-django/config/settings/local.py | 57 ++
chronojumpserver-django/config/urls.py | 38 ++
chronojumpserver-django/config/wsgi.py | 16 +
.../locale/es/LC_MESSAGES/django.po | 335 +++++++++++
chronojumpserver-django/manage.py | 22 +
chronojumpserver/__init__.py | 12 +-
chronojumpserver/models.py | 2 +-
chronojumpserver/templates/groups_and_players.html | 19 +
chronojumpserver/views.py | 7 +
compose/local/django/start.sh | 5 +
compose/local/flask/chronojump.conf.template | 20 +
compose/local/flask/start.sh | 2 +
compose/production/django/entrypoint.sh | 8 +
local.yml | 50 ++
requirements/base.txt | 26 +
requirements/django/base.txt | 18 +
requirements/django/local.txt | 25 +
requirements/local.txt | 5 +
requirements/production.txt | 4 +
78 files changed, 4023 insertions(+), 4 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 265c921..fdffc43 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,7 +52,6 @@ docs/_build/
# PyBuilder
target/
-
Dockerfile
chronojump.conf.example
chronojumpserver/static/images/photos/
diff --git a/chronojumpserver-django/babel.cfg b/chronojumpserver-django/babel.cfg
new file mode 100644
index 0000000..f0234b3
--- /dev/null
+++ b/chronojumpserver-django/babel.cfg
@@ -0,0 +1,3 @@
+[python: **.py]
+[jinja2: **/templates/**.html]
+extensions=jinja2.ext.autoescape,jinja2.ext.with_
diff --git a/chronojumpserver-django/chronojump_networks/__init__.py
b/chronojumpserver-django/chronojump_networks/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/chronojumpserver-django/chronojump_networks/core/__init__.py
b/chronojumpserver-django/chronojump_networks/core/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/chronojumpserver-django/chronojump_networks/core/models.py
b/chronojumpserver-django/chronojump_networks/core/models.py
new file mode 100644
index 0000000..dfa6ca8
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/core/models.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+
+import uuid as uuid_lib
+
+class TimeStampedModel(models.Model):
+ """
+ An abstract base class model that provides self-
+ updating ``created`` and ``modified`` fields.
+ """
+ created = models.DateTimeField(auto_now_add=True)
+ modified = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ abstract = True
+
+@python_2_unicode_compatible # For Python 3.5+ and 2.7
+class ChronojumpBaseModel(models.Model):
+ """
+ An abstract base class model that provides self-
+ updating ``created`` and ``modified`` fields.
+ """
+ name = models.CharField(max_length=50)
+
+ class Meta:
+ abstract = True
+
+ def __str__(self):
+ return self.name
+
+class ChronojumpBaseModelWithUUID(ChronojumpBaseModel):
+ class Meta:
+ abstract = True
+
+ slug = models.SlugField(unique=True) # Used to find the web URL
+ uuid = models.UUIDField( # Used by the API to look up the record
+ db_index=True,
+ default=uuid_lib.uuid4,
+ editable=False)
diff --git a/chronojumpserver-django/chronojump_networks/organizations/__init__.py
b/chronojumpserver-django/chronojump_networks/organizations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/chronojumpserver-django/chronojump_networks/organizations/admin.py
b/chronojumpserver-django/chronojump_networks/organizations/admin.py
new file mode 100644
index 0000000..4a25270
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/admin.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.contrib import admin
+from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
+from django.contrib.auth.forms import UserChangeForm, UserCreationForm
+
+# Register your models here.
+from .models import Organization, OrganizationStaff, Group, GroupCoach
+from .models import Gym, Station, Exercise
+from .models import User
+from .models import Player, GroupPlayer
+
+
+class MyUserChangeForm(UserChangeForm):
+ class Meta(UserChangeForm.Meta):
+ model = User
+
+
+class MyUserCreationForm(UserCreationForm):
+
+ error_message = UserCreationForm.error_messages.update({
+ 'duplicate_username': 'This username has already been taken.'
+ })
+
+ class Meta(UserCreationForm.Meta):
+ model = User
+
+ def clean_username(self):
+ username = self.cleaned_data["username"]
+ try:
+ User.objects.get(username=username)
+ except User.DoesNotExist:
+ return username
+ raise forms.ValidationError(self.error_messages['duplicate_username'])
+
+
+@admin.register(User)
+class MyUserAdmin(AuthUserAdmin):
+ form = MyUserChangeForm
+ add_form = MyUserCreationForm
+ fieldsets = (
+ ('User Profile', {'fields': ('name','language')}),
+ ) + AuthUserAdmin.fieldsets
+ list_display = ('username', 'name', 'organization_name', 'is_superuser')
+ search_fields = ['name']
+
+
+class OrganizationStaffInline(admin.TabularInline):
+ model = OrganizationStaff
+ extra = 1
+
+
+class OrganizationGroups(admin.TabularInline):
+ model = Group
+ extra = 1
+
+@admin.register(Organization)
+class OrganizationModelAdmin(admin.ModelAdmin):
+
+ list_display = ( 'name', 'country')
+ inlines = (OrganizationStaffInline, OrganizationGroups)
+
+class GroupCoachInline(admin.TabularInline):
+ model = GroupCoach
+ extra = 1
+
+class GroupPlayerInline(admin.TabularInline):
+ model = GroupPlayer
+ extra = 1
+
+
+@admin.register(Group)
+class GroupModelAdmin(admin.ModelAdmin):
+ list_display = ( 'name', 'organization', 'responsible', 'gym')
+ list_filter = ('organization', 'gym')
+
+ inlines = (GroupCoachInline, GroupPlayerInline,)
+
+
+@admin.register(Gym)
+class GymModelAdmin(admin.ModelAdmin):
+
+ list_display = ( 'name', 'organization', 'responsible')
+ list_filter = ('organization',)
+
+@admin.register(Player)
+class PlayerModelAdmin(admin.ModelAdmin):
+ list_display = ('number', 'name', 'organization')
+ list_filter = ('organization',)
+
+
+class ExerciseInlineAdmin(admin.TabularInline):
+ model = Exercise
+ extra = 1
+
+@admin.register(Station)
+class StationModelAdmin(admin.ModelAdmin):
+ list_display = ( 'name', 'organization', 'gym', 'type')
+ list_filter = ('gym', 'type')
+ inlines = [
+ ExerciseInlineAdmin
+ ]
+
+
+@admin.register(Exercise)
+class ExerciseModelAdmin(admin.ModelAdmin):
+ list_display = ( 'name', 'station', 'percentBodyMassDisplaced')
diff --git a/chronojumpserver-django/chronojump_networks/organizations/api/__init__.py
b/chronojumpserver-django/chronojump_networks/organizations/api/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/api/__init__.py
@@ -0,0 +1 @@
+
diff --git a/chronojumpserver-django/chronojump_networks/organizations/api/permissions.py
b/chronojumpserver-django/chronojump_networks/organizations/api/permissions.py
new file mode 100644
index 0000000..b638092
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/api/permissions.py
@@ -0,0 +1,9 @@
+from rest_framework import permissions
+from django.utils.translation import ugettext_lazy as _
+
+class IsCoachOfTheGroup(permissions.BasePermission):
+ message = _('You are not a valid coach for this group. This will be notify')
+
+ def has_permission(self, request, view):
+ # TODO: Check that user is a coach and can see the group
+ return True
diff --git a/chronojumpserver-django/chronojump_networks/organizations/api/serializers.py
b/chronojumpserver-django/chronojump_networks/organizations/api/serializers.py
new file mode 100644
index 0000000..d11ce78
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/api/serializers.py
@@ -0,0 +1,35 @@
+from rest_framework import serializers
+
+from ..models import Player, Station, Exercise, Gym
+
+from chronojump_networks.tasks.api.serializers import PlayerTaskSerializer
+
+class PlayerSerializer(serializers.ModelSerializer):
+ player_tasks = PlayerTaskSerializer(many=True)
+ class Meta:
+ model = Player
+ fields = [ 'id', 'name', 'number', 'height', 'weight', 'image', 'player_tasks']
+
+
+class GymSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Gym
+ field = [ 'id', 'name', 'responsible']
+
+
+class StationSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Station
+ fields = [ 'id', 'name', 'type' ]
+
+class ExerciseSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Exercise
+ fields = [ 'id', 'name', 'percentBodyMassDisplaced' ]
+
+
+class GymStationsSerializer(serializers.ModelSerializer):
+ exercises = ExerciseSerializer(many=True)
+ class Meta:
+ model = Station
+ fields = [ 'id', 'name', 'type', 'exercises']
diff --git a/chronojumpserver-django/chronojump_networks/organizations/api/urls.py
b/chronojumpserver-django/chronojump_networks/organizations/api/urls.py
new file mode 100644
index 0000000..e9c4342
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/api/urls.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.conf.urls import url
+
+from . import views
+
+urlpatterns = [
+ url(
+ regex=r'^(?P<organization_id>\d+)/(?P<group_id>\d+)/players$',
+ view=views.GroupPlayerListGenericAPIView.as_view(),
+ name='group_players_list'
+ ),
+ url(
+ regex=r'^(?P<organization_id>\d+)/(?P<group_id>\d+)/add_players$',
+ view=views.GroupAddPlayerListView.as_view(),
+ name='group_add_players_list'
+ ),
+ url(
+ regex=r'^(?P<organization_id>\d+)/(?P<group_id>\d+)/remove_players$',
+ view=views.RemovePlayerFromGroupDestroyView.as_view(),
+ name='remove_players_from_group'
+ ),
+ url(
+ regex=r'^(?P<organization_id>\d+)/players/$',
+ view=views.PlayerListView.as_view(),
+ name='players_list'
+ ),
+ # Get Stations from Gym
+ url(
+ regex=r'^(?P<organization_id>\d+)/gym_stations/$',
+ view=views.GymStationsListView.as_view(),
+ name='gym_stations'
+ ),
+ url(
+ regex=r'^(?P<organization_id>\d+)/station_exercises/$',
+ view=views.StationExercisesListView.as_view(),
+ name='station_exercises'
+ ),
+ url(
+ regex=r'^(?P<organization_id>\d+)/gym_stations/(?P<gym_id>\d+)$',
+ view=views.GymStationsListView.as_view(),
+ name='gym_stations'
+ ),
+]
diff --git a/chronojumpserver-django/chronojump_networks/organizations/api/views.py
b/chronojumpserver-django/chronojump_networks/organizations/api/views.py
new file mode 100644
index 0000000..2f70b8b
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/api/views.py
@@ -0,0 +1,112 @@
+from ..models import Player, GroupPlayer, Group, Station, Gym, Exercise
+from ..decorators import check_user_organization
+from .serializers import PlayerSerializer, StationSerializer, ExerciseSerializer, GymStationsSerializer
+
+from django.http import HttpResponse, JsonResponse
+from django.shortcuts import get_object_or_404
+from rest_framework.response import Response
+from rest_framework.generics import GenericAPIView, ListAPIView, ListCreateAPIView, DestroyAPIView,
RetrieveUpdateDestroyAPIView
+from rest_framework.mixins import ListModelMixin, CreateModelMixin, DestroyModelMixin
+from rest_framework.permissions import IsAuthenticated
+
+from .permissions import IsCoachOfTheGroup
+
+
+class GroupPlayerListGenericAPIView(ListAPIView):
+ permission_classes = (IsAuthenticated, IsCoachOfTheGroup)
+ serializer_class = PlayerSerializer
+
+ def get_queryset(self):
+ group_id = int(self.kwargs['group_id'])
+ group = Group.objects.get(id=group_id)
+ return [p.player for p in group.players.all()]
+
+class GroupAddPlayerListView(ListCreateAPIView):
+ """ Class to get the available players that can be added to the
+ group """
+ permission_classes = (IsAuthenticated, IsCoachOfTheGroup)
+ serializer_class = PlayerSerializer
+
+ def get_queryset(self):
+ group_id = int(self.kwargs['group_id'])
+ organization_id = int(self.kwargs['organization_id'])
+ group = Group.objects.get(id=group_id)
+ players_ids = [p.player.id for p in group.players.all()]
+ return Player.objects.filter(organization_id=organization_id).exclude(id__in=players_ids)
+
+ def create(self, request, *args, **kwargs):
+ group_id = int(self.kwargs['group_id'])
+ organization_id = int(self.kwargs['organization_id'])
+ # Get the player_ids passed
+ data = dict(request.data)
+ player_ids = data['player_ids[]']
+ for player_id in player_ids:
+ GroupPlayer.objects.create(group_id=group_id, player_id=int(player_id))
+ print("Add player %d to group %d" % (int(player_id), group_id))
+
+ return JsonResponse({}, status=201, safe=False)
+
+class RemovePlayerFromGroupDestroyView(DestroyAPIView):
+ """Remove players from group"""
+ permission_classes = (IsAuthenticated, IsCoachOfTheGroup)
+ serializer_class = PlayerSerializer
+
+ def destroy(self, request, *args, **kwargs):
+ group_id = int(self.kwargs['group_id'])
+ data = dict(request.data)
+ player_ids = data['player_ids[]']
+ for player_id in player_ids:
+ o = GroupPlayer.objects.get(group_id=group_id, player_id=int(player_id))
+ o.delete()
+ print("Remove player %d from group %d" % (int(player_id), group_id))
+ return JsonResponse({}, status=204, safe=False)
+
+
+class GymStationsListView(ListAPIView):
+ """List stations of a gym """
+ permission_classes = (IsAuthenticated, )
+ serializer_class = StationSerializer
+
+ def list(self, request, *args, **kwargs):
+ # Get as dictionary the data given from ajax
+ data = dict(request.GET)
+ gym_id = int(data['gym_id'][0])
+ queryset = Station.objects.filter(gym_id=gym_id)
+ serializer = StationSerializer(queryset, many=True)
+ print(serializer.data)
+ return Response(serializer.data)
+
+class StationExercisesListView(ListAPIView):
+ """List exercises from station """
+ permission_classes = (IsAuthenticated, )
+ serializer_class = ExerciseSerializer
+
+ def list(self, request, *args, **kwargs):
+ # Get as dictionary the data given from ajax
+ data = dict(request.GET)
+ station_id = int(data['station_id'][0])
+ station = get_object_or_404(Station, pk=station_id)
+ queryset = station.exercises.all()
+ serializer = ExerciseSerializer(queryset, many=True)
+ return Response(serializer.data)
+
+
+class GymStationsListView(ListAPIView):
+ """List exercises from station """
+ permission_classes = (IsAuthenticated, )
+ serializer_class = GymStationsSerializer
+
+ def get_queryset(self):
+ # Get as dictionary the data given from ajax
+ gym_id = int(self.kwargs['gym_id'])
+ gym = get_object_or_404(Gym, pk=gym_id)
+ return gym.stations.all()
+
+class PlayerListView(ListCreateAPIView):
+ """Players of the organization"""
+ permission_classes = (IsAuthenticated, )
+ serializer_class = PlayerSerializer
+
+ def get_queryset(self):
+ organization_id = int(self.kwargs['organization_id'])
+ return Player.objects.filter(organization_id=organization_id)
diff --git a/chronojumpserver-django/chronojump_networks/organizations/apps.py
b/chronojumpserver-django/chronojump_networks/organizations/apps.py
new file mode 100644
index 0000000..4406d32
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/apps.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class OrganizationsConfig(AppConfig):
+ name = 'chronojump_networks.organizations'
+ verbose_name = "Organizations"
diff --git a/chronojumpserver-django/chronojump_networks/organizations/decorators.py
b/chronojumpserver-django/chronojump_networks/organizations/decorators.py
new file mode 100644
index 0000000..dd59f89
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/decorators.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from functools import wraps
+
+
+from django.http import HttpResponseForbidden
+from django.utils.translation import ugettext_lazy as _
+
+def check_user_organization(view_func):
+ """Check that user belongs to the same organization."""
+
+ @wraps(view_func)
+ def new_view_func(request, *args, **kwargs):
+ if request.user.organization().id == int(kwargs['organization_id']):
+ response = view_func(request, *args, **kwargs)
+
+ return response
+ else:
+ return HttpResponseForbidden(_("The user does not belongs to this organization."))
+
+ return new_view_func
diff --git a/chronojumpserver-django/chronojump_networks/organizations/forms.py
b/chronojumpserver-django/chronojump_networks/organizations/forms.py
new file mode 100644
index 0000000..2bcb4e9
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/forms.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+from crispy_forms.helper import FormHelper
+
+from .models import User
+
+class UserLoginForm(forms.Form):
+
+ username = forms.CharField(
+ # Translators: Username for login form
+ label = _("Username"),
+ max_length = 25,
+ required = True
+ )
+
+ password = forms.CharField(
+ # Translators: Password for login form
+ label = _("Password"),
+ max_length=25,
+ required = True,
+ widget=forms.PasswordInput
+ )
+
+ def __init__(self, *args, **kwargs):
+ super(UserLoginForm, self).__init__(*args, **kwargs)
+ self.helper = FormHelper()
diff --git a/chronojumpserver-django/chronojump_networks/organizations/migrations/0001_initial.py
b/chronojumpserver-django/chronojump_networks/organizations/migrations/0001_initial.py
new file mode 100644
index 0000000..0d7a2b9
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/migrations/0001_initial.py
@@ -0,0 +1,191 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2018-03-31 10:07
+from __future__ import unicode_literals
+
+from django.conf import settings
+import django.contrib.auth.models
+import django.contrib.auth.validators
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('auth', '0008_alter_user_username_max_length'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='User',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False,
verbose_name='ID')),
+ ('password', models.CharField(max_length=128, verbose_name='password')),
+ ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
+ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has
all permissions without explicitly assigning them.', verbose_name='superuser status')),
+ ('username', models.CharField(error_messages={'unique': 'A user with that username already
exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.',
max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()],
verbose_name='username')),
+ ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
+ ('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')),
+ ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
+ ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can
log into this admin site.', verbose_name='staff status')),
+ ('is_active', models.BooleanField(default=True, help_text='Designates whether this user
should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
+ ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date
joined')),
+ ('name', models.CharField(blank=True, max_length=100, verbose_name='Name of User')),
+ ('language', models.CharField(choices=[('es', 'Spanish'), ('ca', 'Catalan'), ('fr',
'French'), ('en', 'English')], default='en', max_length=2)),
+ ],
+ options={
+ 'db_table': 'user',
+ },
+ managers=[
+ ('objects', django.contrib.auth.models.UserManager()),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Exercise',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False,
verbose_name='ID')),
+ ('name', models.CharField(max_length=50)),
+ ('percentBodyMassDisplaced', models.FloatField(default=0, null=True)),
+ ],
+ options={
+ 'db_table': 'exercise',
+ },
+ ),
+ migrations.CreateModel(
+ name='Group',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False,
verbose_name='ID')),
+ ('name', models.CharField(max_length=50)),
+ ],
+ options={
+ 'db_table': 'group_',
+ },
+ ),
+ migrations.CreateModel(
+ name='GroupCoach',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False,
verbose_name='ID')),
+ ('coach', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='groups_by_coach', to=settings.AUTH_USER_MODEL)),
+ ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='coaches', to='organizations.Group')),
+ ],
+ options={
+ 'db_table': 'groupCoach',
+ },
+ ),
+ migrations.CreateModel(
+ name='GroupPlayer',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False,
verbose_name='ID')),
+ ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='players', to='organizations.Group')),
+ ],
+ options={
+ 'db_table': 'groupPerson',
+ },
+ ),
+ migrations.CreateModel(
+ name='Gym',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False,
verbose_name='ID')),
+ ('name', models.CharField(max_length=50)),
+ ],
+ options={
+ 'db_table': 'gym',
+ },
+ ),
+ migrations.CreateModel(
+ name='Organization',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False,
verbose_name='ID')),
+ ('name', models.CharField(max_length=50)),
+ ('country', models.CharField(blank=True, max_length=20, null=True)),
+ ('image', models.FileField(blank=True, upload_to='organizations')),
+ ('responsible', models.OneToOneField(blank=True, null=True,
on_delete=django.db.models.deletion.SET_NULL, related_name='responsible_of', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'db_table': 'organization',
+ },
+ ),
+ migrations.CreateModel(
+ name='OrganizationStaff',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False,
verbose_name='ID')),
+ ('role', models.IntegerField(choices=[(1, 'Coach'), (2, 'Coach Leader')])),
+ ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='users', to='organizations.Organization')),
+ ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE,
related_name='member_of', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'db_table': 'organizationStaff',
+ },
+ ),
+ migrations.CreateModel(
+ name='Player',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False,
verbose_name='ID')),
+ ('name', models.CharField(max_length=50)),
+ ('height', models.FloatField(default=0)),
+ ('weight', models.FloatField(default=0)),
+ ('rfid', models.CharField(max_length=23, unique=True)),
+ ('image', models.FileField(blank=True, null=True, upload_to='players')),
+ ('is_available', models.BooleanField(default=True)),
+ ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='players', to='organizations.Organization')),
+ ],
+ options={
+ 'db_table': 'person',
+ },
+ ),
+ migrations.CreateModel(
+ name='Station',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False,
verbose_name='ID')),
+ ('name', models.CharField(max_length=50)),
+ ('type', models.CharField(choices=[('S', 'Sprint'), ('I', 'Inertial'), ('G',
'Gravitatory')], max_length=1)),
+ ('gym', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='stations', to='organizations.Gym')),
+ ],
+ options={
+ 'db_table': 'station',
+ },
+ ),
+ migrations.AddField(
+ model_name='gym',
+ name='organization',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gyms',
to='organizations.Organization'),
+ ),
+ migrations.AddField(
+ model_name='gym',
+ name='responsible',
+ field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
related_name='responsible_of_gym', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AddField(
+ model_name='groupplayer',
+ name='player',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups',
to='organizations.Player'),
+ ),
+ migrations.AddField(
+ model_name='group',
+ name='organization',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups',
to='organizations.Organization'),
+ ),
+ migrations.AddField(
+ model_name='group',
+ name='responsible',
+ field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
related_name='responsible_of_group', to='organizations.OrganizationStaff'),
+ ),
+ migrations.AddField(
+ model_name='exercise',
+ name='station',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='exercises',
to='organizations.Station'),
+ ),
+ migrations.AddField(
+ model_name='user',
+ name='groups',
+ field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will
get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user',
to='auth.Group', verbose_name='groups'),
+ ),
+ migrations.AddField(
+ model_name='user',
+ name='user_permissions',
+ field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.',
related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
+ ),
+ ]
diff --git a/chronojumpserver-django/chronojump_networks/organizations/migrations/0002_auto_20180331_1224.py
b/chronojumpserver-django/chronojump_networks/organizations/migrations/0002_auto_20180331_1224.py
new file mode 100644
index 0000000..6debeb7
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/migrations/0002_auto_20180331_1224.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2018-03-31 12:24
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('organizations', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='player',
+ name='number',
+ field=models.IntegerField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name='organizationstaff',
+ name='role',
+ field=models.IntegerField(choices=[(1, 'Coach'), (2, 'Coach Lead')]),
+ ),
+ ]
diff --git a/chronojumpserver-django/chronojump_networks/organizations/migrations/0003_auto_20180331_1259.py
b/chronojumpserver-django/chronojump_networks/organizations/migrations/0003_auto_20180331_1259.py
new file mode 100644
index 0000000..6c1758e
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/migrations/0003_auto_20180331_1259.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2018-03-31 12:59
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('organizations', '0002_auto_20180331_1224'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='gym',
+ name='responsible',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
related_name='responsible_of_gym', to=settings.AUTH_USER_MODEL),
+ ),
+ ]
diff --git a/chronojumpserver-django/chronojump_networks/organizations/migrations/0004_group_gym.py
b/chronojumpserver-django/chronojump_networks/organizations/migrations/0004_group_gym.py
new file mode 100644
index 0000000..25f354f
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/migrations/0004_group_gym.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2018-04-01 18:18
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('organizations', '0003_auto_20180331_1259'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='group',
+ name='gym',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
related_name='gym_groups', to='organizations.Gym'),
+ ),
+ ]
diff --git a/chronojumpserver-django/chronojump_networks/organizations/migrations/0005_auto_20180401_2146.py
b/chronojumpserver-django/chronojump_networks/organizations/migrations/0005_auto_20180401_2146.py
new file mode 100644
index 0000000..030621a
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/migrations/0005_auto_20180401_2146.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2018-04-01 21:46
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('organizations', '0004_group_gym'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='group',
+ name='responsible',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
related_name='groups_responsible', to='organizations.OrganizationStaff'),
+ ),
+ ]
diff --git a/chronojumpserver-django/chronojump_networks/organizations/migrations/0006_auto_20180402_1751.py
b/chronojumpserver-django/chronojump_networks/organizations/migrations/0006_auto_20180402_1751.py
new file mode 100644
index 0000000..d95b530
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/migrations/0006_auto_20180402_1751.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2018-04-02 17:51
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('organizations', '0005_auto_20180401_2146'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='group',
+ name='responsible',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
related_name='groups_responsible', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AlterField(
+ model_name='organizationstaff',
+ name='organization',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staff',
to='organizations.Organization'),
+ ),
+ ]
diff --git a/chronojumpserver-django/chronojump_networks/organizations/migrations/__init__.py
b/chronojumpserver-django/chronojump_networks/organizations/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/chronojumpserver-django/chronojump_networks/organizations/models.py
b/chronojumpserver-django/chronojump_networks/organizations/models.py
new file mode 100644
index 0000000..688b119
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/models.py
@@ -0,0 +1,242 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.core.exceptions import ObjectDoesNotExist
+from django.contrib.auth.models import AbstractUser
+from django.db import models
+from django.urls import reverse
+from django.utils.translation import ugettext_lazy as _
+
+
+
+# Create your models here.
+from chronojump_networks.core.models import ChronojumpBaseModel, ChronojumpBaseModelWithUUID
+
+
+
+class User(AbstractUser):
+ # Language Code
+ LANGUAGE_SPANISH = 'es'
+ LANGUAGE_CATALAN = 'ca'
+ LANGUAGE_FRENCH = 'fr'
+ LANGUAGE_ENGLISH = 'en'
+
+ LANGUAGE_CHOICES = (
+ (LANGUAGE_SPANISH, _('Spanish')),
+ (LANGUAGE_CATALAN, _('Catalan')),
+ (LANGUAGE_FRENCH, _('French')),
+ (LANGUAGE_ENGLISH, _('English'))
+ )
+
+ class Meta:
+ db_table = "user"
+
+ # First Name and Last Name do not cover name patterns
+ # around the globe.
+ name = models.CharField(_('Name of User'), blank=True, max_length=100)
+ # Languages
+ language = models.CharField(
+ max_length=2,
+ choices=LANGUAGE_CHOICES,
+ default=LANGUAGE_ENGLISH
+ )
+ #
+
+
+ def __str__(self):
+ return ""
+
+ def __unicode__(self):
+ return u'%s' % self.name
+
+ def get_absolute_url(self):
+ return reverse('users:detail', kwargs={'username': self.username})
+
+
+ def organization(self):
+ """ Check if user is a member of an organization """
+ try:
+ org_staff = OrganizationStaff.objects.get(user=self.id)
+ return org_staff.organization
+ except ObjectDoesNotExist:
+ return None
+
+ def is_organization_member(self):
+ """ Check if user is a member of an organization """
+ if self.organization():
+ return True
+ else:
+ return False
+
+ def organization_name(self):
+ """ Check if user is a member of an organization """
+ organization = self.organization()
+ if organization:
+ return organization.name
+ else:
+ return ""
+
+
+class Organization(ChronojumpBaseModel):
+ class Meta:
+ db_table = 'organization'
+
+ name = models.CharField(max_length=50)
+ country = models.CharField(max_length=20, null=True, blank=True)
+ image = models.FileField(upload_to='organizations', blank=True)
+ responsible = models.OneToOneField(User,
+ on_delete=models.SET_NULL,
+ blank=True,
+ null=True,
+ related_name='responsible_of')
+
+
+class OrganizationStaff(models.Model):
+ ROLE_COACH_LEADER = 2
+ ROLE_COACH = 1
+
+ ROLE_CHOICES = (
+ (ROLE_COACH, _('Coach')),
+ (ROLE_COACH_LEADER, _('Coach Lead'))
+ )
+
+ role = models.IntegerField(
+ choices=ROLE_CHOICES
+ )
+
+ class Meta:
+ db_table = 'organizationStaff'
+
+ user = models.OneToOneField(User,
+ on_delete=models.CASCADE,
+ related_name='member_of')
+ organization = models.ForeignKey(Organization,
+ on_delete=models.CASCADE,
+ related_name='staff')
+
+ role = models.IntegerField(choices=ROLE_CHOICES)
+
+ def __str__(self):
+ return self.user.name
+
+
+
+class Gym(ChronojumpBaseModel):
+ """Gym Model"""
+
+ class Meta:
+ db_table = "gym"
+
+ organization = models.ForeignKey(Organization,
+ on_delete=models.CASCADE,
+ related_name='gyms')
+
+ responsible = models.ForeignKey(User,
+ on_delete=models.SET_NULL,
+ blank=True,
+ null=True,
+ related_name='responsible_of_gym')
+
+
+
+
+
+class Station(ChronojumpBaseModel):
+ """Station Model"""
+
+ STATION_INERTIAL = 'I'
+ STATION_GRAVITATORY = 'G'
+ STATION_SPRINT = 'S'
+
+ STATION_CHOICES = (
+ (STATION_SPRINT, _('Sprint')),
+ (STATION_INERTIAL, _('Inertial')),
+ (STATION_GRAVITATORY, _('Gravitatory'))
+ )
+
+ class Meta:
+ db_table = 'station'
+
+ gym = models.ForeignKey(Gym,
+ on_delete=models.CASCADE,
+ related_name='stations')
+ type = models.CharField(max_length=1, choices = STATION_CHOICES)
+
+ def organization(self):
+ return self.gym.organization
+
+class Exercise(ChronojumpBaseModel):
+ """Exercise Model"""
+
+ class Meta:
+ db_table = 'exercise'
+
+ station = models.ForeignKey(Station,
+ on_delete=models.CASCADE,
+ related_name='exercises')
+
+ percentBodyMassDisplaced = models.FloatField(null=True, default=0)
+
+
+class Player(ChronojumpBaseModel):
+
+ class Meta:
+ db_table = 'person'
+
+ organization = models.ForeignKey(Organization,
+ on_delete=models.CASCADE,
+ related_name='players')
+
+ height = models.FloatField(default=0)
+ weight = models.FloatField(default=0)
+ rfid = models.CharField(max_length=23, unique=True)
+ image = models.FileField(upload_to='players', null=True, blank=True)
+ is_available = models.BooleanField(default=True)
+ number = models.IntegerField(null=True,blank=True)
+
+
+class Group(ChronojumpBaseModel):
+
+ class Meta:
+ db_table = 'group_'
+
+ organization = models.ForeignKey(Organization,
+ on_delete = models.CASCADE,
+ related_name="groups")
+ responsible = models.ForeignKey(User,
+ on_delete=models.SET_NULL,
+ blank=True,
+ null=True,
+ related_name='groups_responsible')
+
+ gym = models.ForeignKey(Gym,
+ on_delete=models.SET_NULL,
+ related_name="gym_groups",
+ blank=True,
+ null=True)
+
+
+class GroupCoach(models.Model):
+
+ class Meta:
+ db_table = 'groupCoach'
+
+ coach = models.ForeignKey(User,
+ on_delete=models.CASCADE,
+ related_name="groups_by_coach")
+ group = models.ForeignKey(Group,
+ on_delete=models.CASCADE,
+ related_name="coaches")
+
+
+class GroupPlayer(models.Model):
+ class Meta:
+ db_table = 'groupPerson'
+
+ player = models.ForeignKey(Player,
+ on_delete=models.CASCADE,
+ related_name="groups")
+
+ group = models.ForeignKey(Group,
+ on_delete=models.CASCADE,
+ related_name="players")
diff --git a/chronojumpserver-django/chronojump_networks/organizations/tests.py
b/chronojumpserver-django/chronojump_networks/organizations/tests.py
new file mode 100644
index 0000000..5982e6b
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/tests.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/chronojumpserver-django/chronojump_networks/organizations/urls.py
b/chronojumpserver-django/chronojump_networks/organizations/urls.py
new file mode 100644
index 0000000..5ff3cf6
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/urls.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.conf.urls import url, include
+from django.contrib.auth.decorators import login_required
+from django.views.generic import TemplateView
+
+from . import views
+
+app_name = 'organizations'
+urlpatterns = [
+ url(
+ regex=r'^$',
+ view=login_required(TemplateView.as_view(template_name='pages/index.html')),
+ name='index'),
+ url(
+ regex=r'^$',
+ view=views.OrganizationListView.as_view(),
+ name='list'
+ ),
+ url(
+ regex=r'^(?P<id>[\w.@+-]+)/$',
+ view=views.OrganizationDetailView.as_view(),
+ name='detail'
+ ),
+ url(
+ regex=r'^stations/$',
+ view=views.StationListView.as_view(),
+ name='stations'
+ ),
+ url(
+ regex=r'^(?P<organization_id>\d+)/groups/(?P<group_id>\d+)$',
+ view=views.GroupPlayerTemplateView.as_view(),
+ name='group_players_list'
+ ),
+ url(
+ regex=r'^(?P<organization_id>\d+)/gyms/(?P<gym_id>\d+)$',
+ view=views.GymDetailTemplateView.as_view(),
+ name='gym_detail'
+ ),
+ url(
+ regex=r'^login$',
+ view=views.organization_login,
+ name='login'
+ ),
+ url(
+ regex=r'^logout$',
+ view=views.organization_logout,
+ name='logout'
+ ),
+]
diff --git a/chronojumpserver-django/chronojump_networks/organizations/views.py
b/chronojumpserver-django/chronojump_networks/organizations/views.py
new file mode 100644
index 0000000..465cfd3
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/organizations/views.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.contrib.auth import authenticate
+from django.contrib.auth import login as auth_login, logout as auth_logout
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.shortcuts import render, redirect
+from django.urls import reverse
+from django.views.generic import DetailView, ListView, RedirectView, UpdateView, TemplateView
+from django.utils.translation import ugettext_lazy as _
+from django.utils import translation
+
+from .models import Organization, Station, Group, Gym, Player
+from .forms import UserLoginForm
+from .decorators import check_user_organization
+
+from django import http
+from django.conf import settings
+
+class OrganizationDetailView(LoginRequiredMixin, DetailView):
+ model = Organization
+ slug_field = 'id'
+ slug_url_kwarg = 'id'
+
+
+class OrganizationListView(LoginRequiredMixin, ListView):
+ model = Organization
+ slug_field = 'name'
+ slug_url_kwarg = 'name'
+
+class StationListView(LoginRequiredMixin, ListView):
+ model = Station
+ slug_field = 'name'
+ slug_url_kwarg = 'name'
+
+
+class GroupPlayerTemplateView(LoginRequiredMixin, TemplateView):
+ """ Show the Group and Player List
+ """
+ template_name = 'organizations/groups/group_players_list.html'
+
+ def get_context_data(self, **kwargs):
+ """Pass to the template the Group object"""
+
+ context = super(GroupPlayerTemplateView,self).get_context_data(**kwargs)
+ group_id = int(kwargs['group_id'])
+ context['group'] = Group.objects.get(id=group_id)
+ return context
+
+class GymDetailTemplateView(LoginRequiredMixin, TemplateView):
+ template_name = 'organizations/gyms/gym_detail.html'
+
+ def get_context_data(self, **kwargs):
+ """Pass to the template the Group object"""
+
+ context = super(GymDetailTemplateView,self).get_context_data(**kwargs)
+ gym_id = int(kwargs['gym_id'])
+ context['gym'] = Gym.objects.get(id=gym_id)
+ return context
+
+
+def organization_login(request):
+ """ Login authentication method against the organization """
+ # If user is already authenticated , got to index
+ if request.user.is_authenticated():
+ return redirect('/')
+ # Create user login form with data from post or none if method is get
+ form = UserLoginForm(request.POST or None)
+ if request.method == 'GET':
+ return render(request, 'users/login.html', {'form': form})
+ else:
+ if form.is_valid():
+ username = form.cleaned_data['username']
+ password = form.cleaned_data['password']
+ user = authenticate(username=username, password=password)
+ if user is not None:
+ auth_login(request, user)
+ # Set the language of the User
+ user_language = user.language
+ translation.activate(user_language)
+ request.session[translation.LANGUAGE_SESSION_KEY] = user_language
+ #print(settings.LANGUAGE_CODE)
+ print(translation.get_language())
+ return redirect('/')
+ else:
+ error_msg = _("Invalid username or password")
+ return render(request,
+ 'users/login.html',
+ {'form': form, 'error_msg' : error_msg})
+
+def organization_logout(request):
+ auth_logout(request)
+ return redirect('/')
diff --git a/chronojumpserver-django/chronojump_networks/results/__init__.py
b/chronojumpserver-django/chronojump_networks/results/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/chronojumpserver-django/chronojump_networks/results/admin.py
b/chronojumpserver-django/chronojump_networks/results/admin.py
new file mode 100644
index 0000000..13be29d
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/results/admin.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.contrib import admin
+
+# Register your models here.
diff --git a/chronojumpserver-django/chronojump_networks/results/apps.py
b/chronojumpserver-django/chronojump_networks/results/apps.py
new file mode 100644
index 0000000..383f243
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/results/apps.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class ResultsConfig(AppConfig):
+ name = 'results'
diff --git a/chronojumpserver-django/chronojump_networks/results/migrations/__init__.py
b/chronojumpserver-django/chronojump_networks/results/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/chronojumpserver-django/chronojump_networks/results/models.py
b/chronojumpserver-django/chronojump_networks/results/models.py
new file mode 100644
index 0000000..1dfab76
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/results/models.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models
+
+# Create your models here.
diff --git a/chronojumpserver-django/chronojump_networks/results/tests.py
b/chronojumpserver-django/chronojump_networks/results/tests.py
new file mode 100644
index 0000000..5982e6b
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/results/tests.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/chronojumpserver-django/chronojump_networks/results/views.py
b/chronojumpserver-django/chronojump_networks/results/views.py
new file mode 100644
index 0000000..e784a0b
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/results/views.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/chronojumpserver-django/chronojump_networks/static/css/styles-airport.css
b/chronojumpserver-django/chronojump_networks/static/css/styles-airport.css
new file mode 100644
index 0000000..7c8375e
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/static/css/styles-airport.css
@@ -0,0 +1,82 @@
+/*
+* AIRPORT PAGE
+*/
+
+#airportTable {
+ border: none;
+}
+
+#airportTable thead tr th {
+ /*border-bottom: none;*/
+}
+
+#airportTable tbody tr td {
+ /*border-top: none;*/
+}
+
+.playerStationCell {
+
+}
+
+.playerWithActiveTasks {
+ /* Permalink - use to edit and share this gradient:
http://colorzilla.com/gradient-editor/#00894e+0,006e2e+100 */
+background: #00894e; /* Old browsers */
+background: -moz-linear-gradient(top, #00894e 0%, #006e2e 100%); /* FF3.6-15 */
+background: -webkit-linear-gradient(top, #00894e 0%,#006e2e 100%); /* Chrome10-25,Safari5.1-6 */
+background: linear-gradient(to bottom, #00894e 0%,#006e2e 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+,
Safari7+ */
+filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00894e',
endColorstr='#006e2e',GradientType=0 ); /* IE6-9 */
+}
+
+#airportTable td.playerName {
+ font-size: 18px;
+ font-weight: bold;
+ text-align: right;
+ vertical-align: middle;
+ border-left: none;
+ border-bottom: none;
+ border-top: none;
+}
+#airportTable th.stationName {
+ text-align: center;
+ background-color: #eee;
+
+}
+
+.carousel-indicators {
+ bottom: 10px;
+}
+
+.carousel-indicators li {
+ border-color: #000;
+}
+
+.carousel-indicators li.active {
+ background-color: gray;
+}
+
+.navbar-text.airport-title {
+ color: #fff;
+ font-size: 1.5em;
+ font-weight: bold;
+ padding-top: 10px;
+}
+
+#datatable_wrapper {
+ margin-top: 20px;
+ padding: 10px;
+ border-radius: 4px 4px 4px 4px;
+ -moz-border-radius: 4px 4px 4px 4px;
+ -webkit-border-radius: 4px 4px 4px 4px;
+ border: 1px solid rgba(240,235,240,1);
+ -webkit-box-shadow: 4px 4px 5px 0px rgba(240,235,240,1);
+ -moz-box-shadow: 4px 4px 5px 0px rgba(240,235,240,1);
+ box-shadow: 4px 4px 5px 0px rgba(240,235,240,1);
+}
+
+td.details-control {
+ background: url('/static/images/details_open.png') no-repeat center center;
+ cursor: pointer;
+}
+tr.shown td.details-control {
+ background: url('/static/images/details_close.png') no-repeat center center;
+}
diff --git a/chronojumpserver-django/chronojump_networks/static/css/styles.css
b/chronojumpserver-django/chronojump_networks/static/css/styles.css
new file mode 100644
index 0000000..637f9cf
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/static/css/styles.css
@@ -0,0 +1,132 @@
+/**
+ Global stylesheet for Chronojump Site
+*/
+
+
+/* Stylesheet from chronojump home page */
+
+body {
+ font-family: 'Open Sans', sans-serif;
+ padding-bottom: 70px;
+ margin-top: 0px;
+}
+
+/* Header Index */
+.header-index {
+ background-color: #0f2351;
+ height: 200px;
+}
+
+.header-index h1, .header-index h4 {
+ color: #fff;
+ font-weight: bold;
+}
+
+body.airport {
+ padding-bottom: 0px;
+}
+
+body.home {
+ margin-top: 0px;
+}
+
+body.login {
+ background-color: #e3e3e3;
+}
+
+/* Buttons */
+.btn-primary {
+ background-color: #0f2351;
+}
+
+.btn-secondary.btn-primary {
+ background-color: #0f2351;
+}
+
+.pagination>.active {
+ background-color: #0f2351;
+}
+
+/* Navbars */
+.navbar-fixed-top {
+ background-color: #0f2351;
+ height: 80px;
+}
+
+.navbar-collapse {
+ background-color: #0f2351;
+}
+
+
+/* Datatables */
+div.datatables_wrapped {
+
+ margin: 20px 0px;
+ padding: 20px 5px;
+ border-radius: 4px 4px 4px 4px;
+ -moz-border-radius: 4px 4px 4px 4px;
+ -webkit-border-radius: 4px 4px 4px 4px;
+ border: 1px solid rgba(240,235,240,1);
+ -webkit-box-shadow: 4px 4px 5px 0px rgba(240,235,240,1);
+ -moz-box-shadow: 4px 4px 5px 0px rgba(240,235,240,1);
+ box-shadow: 4px 4px 5px 0px rgba(240,235,240,1);
+}
+
+
+.footer {
+ background-color: #2b2b2b;
+ color: #ffffff;
+}
+
+.task-link {
+ cursor: pointer;
+}
+
+#results .colResult {
+ color: blue;
+}
+
+.dt-buttons > a.btn {
+ margin-right: 4px;
+ border-radius: 4px;
+
+}
+
+.colAlignRight {
+ text-align: right;
+}
+
+#players td {
+ vertical-align: middle;
+}
+
+#stations td {
+ vertical-align: middle;
+}
+
+#stations tr.selected {
+ font-weight: bold;
+}
+
+.modal-header {
+ background-color: #0f2351;
+ color: #fff;
+}
+
+.modal-header > button.close {
+ color: #fff;
+}
+
+/* Page Header */
+.page-header {
+ border-bottom: 1px solid #0f2351;
+ margin: 10px 5px;
+}
+
+td.details-control {
+ background: url('/static/images/details_open.png') no-repeat center center;
+ cursor: pointer;
+}
+tr.shown td.details-control {
+ background: url('/static/images/details_close.png') no-repeat center center;
+}
diff --git a/chronojumpserver-django/chronojump_networks/static/images/chronojump-logo.png
b/chronojumpserver-django/chronojump_networks/static/images/chronojump-logo.png
new file mode 100644
index 0000000..e4fbbd4
Binary files /dev/null and b/chronojumpserver-django/chronojump_networks/static/images/chronojump-logo.png
differ
diff --git a/chronojumpserver-django/chronojump_networks/static/images/details_close.png
b/chronojumpserver-django/chronojump_networks/static/images/details_close.png
new file mode 100644
index 0000000..9c7d698
Binary files /dev/null and b/chronojumpserver-django/chronojump_networks/static/images/details_close.png
differ
diff --git a/chronojumpserver-django/chronojump_networks/static/images/details_open.png
b/chronojumpserver-django/chronojump_networks/static/images/details_open.png
new file mode 100644
index 0000000..c0edf44
Binary files /dev/null and b/chronojumpserver-django/chronojump_networks/static/images/details_open.png differ
diff --git a/chronojumpserver-django/chronojump_networks/static/js/chronojump.js
b/chronojumpserver-django/chronojump_networks/static/js/chronojump.js
new file mode 100644
index 0000000..58fbfc3
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/static/js/chronojump.js
@@ -0,0 +1,40 @@
+/* The Global Javascript for the site goes here */
+
+/* Add csrf token on each Ajax Call on the site */
+function csrfSafeMethod(method) {
+ // these HTTP methods do not require CSRF protection
+ return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
+}
+
+$.ajaxSetup({
+ beforeSend: function(xhr, settings) {
+ if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
+ xhr.setRequestHeader("X-CSRFToken", csrftoken);
+ }
+ }
+});
+
+/* Enable tooltips */
+function enableToolTips() {
+ $('[data-toggle="tooltip"]').tooltip();
+ console.log("Chronojump: Enable tooltips");
+}
+
+/* Enable Remove Button Selection */
+function enableCheckboxSelection(selectClass, buttonClass) {
+ var selector = '.' + selectClass;
+ var button = '.' + buttonClass;
+ $(selector).on('change', function() {
+ var totalChecked = $(selector+':checked').length;
+ if (totalChecked > 0) {
+ // Enable button
+ $(button).removeClass('disabled');
+ } else {
+ // Disable button
+ $(button).addClass('disabled');
+ }
+ });
+}
+
+
+console.log("Chronojump Networks ready");
diff --git a/chronojumpserver-django/chronojump_networks/static/js/tasks.js
b/chronojumpserver-django/chronojump_networks/static/js/tasks.js
new file mode 100644
index 0000000..b29117c
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/static/js/tasks.js
@@ -0,0 +1 @@
+/* Javascript code for task creation */
diff --git a/chronojumpserver-django/chronojump_networks/tasks/__init__.py
b/chronojumpserver-django/chronojump_networks/tasks/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/chronojumpserver-django/chronojump_networks/tasks/admin.py
b/chronojumpserver-django/chronojump_networks/tasks/admin.py
new file mode 100644
index 0000000..af0a422
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/tasks/admin.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.contrib import admin
+
+from .models import Task
+
+# Register your models here.
+@admin.register(Task)
+class TaskModelAdmin(admin.ModelAdmin):
+ list_display = ( 'ts', 'person', 'station', 'exercise', 'sets', 'nreps' )
diff --git a/chronojumpserver-django/chronojump_networks/tasks/api/__init__.py
b/chronojumpserver-django/chronojump_networks/tasks/api/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/chronojumpserver-django/chronojump_networks/tasks/api/serializers.py
b/chronojumpserver-django/chronojump_networks/tasks/api/serializers.py
new file mode 100644
index 0000000..db161db
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/tasks/api/serializers.py
@@ -0,0 +1,23 @@
+from rest_framework import serializers
+
+from ..models import Task
+
+class PlayerTaskSerializer(serializers.ModelSerializer):
+ exercise = serializers.StringRelatedField()
+ gym = serializers.StringRelatedField()
+ station = serializers.StringRelatedField()
+
+ class Meta:
+ model = Task
+ fields = [ 'id', 'ts', 'sets', 'nreps', 'load', 'speed', 'gym', 'station', 'exercise']
+
+
+class TaskSerializer(serializers.ModelSerializer):
+
+ class Meta:
+ model = Task
+ fields = [ 'id', 'ts', 'person', 'coach',
+ 'gym', 'station', 'exercise',
+ 'sets', 'nreps', 'load', 'speed',
+ 'percentMaxSpeed', 'lossBySpeed', 'lossByPower',
+ 'comment']
diff --git a/chronojumpserver-django/chronojump_networks/tasks/api/urls.py
b/chronojumpserver-django/chronojump_networks/tasks/api/urls.py
new file mode 100644
index 0000000..e008266
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/tasks/api/urls.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.conf.urls import url
+
+from . import views
+
+urlpatterns = [
+ url(
+ regex=r'^$',
+ view=views.ListCreateAPIView.as_view(),
+ name='tasks'
+ ),
+ url(
+ regex=r'^(?P<task_id>\d+)$',
+ view=views.TaskRetrieveUpdateDestroyAPIView.as_view(),
+ name='tasks'
+ ),
+]
diff --git a/chronojumpserver-django/chronojump_networks/tasks/api/views.py
b/chronojumpserver-django/chronojump_networks/tasks/api/views.py
new file mode 100644
index 0000000..fa7b373
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/tasks/api/views.py
@@ -0,0 +1,52 @@
+from ..models import Task
+from .serializers import TaskSerializer
+
+from django.http import HttpResponse, JsonResponse
+from django.shortcuts import get_object_or_404
+from rest_framework.response import Response
+from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
+from rest_framework.mixins import ListModelMixin, CreateModelMixin, DestroyModelMixin
+from rest_framework.permissions import IsAuthenticated
+
+
+class ListCreateAPIView(ListCreateAPIView):
+ permission_classes = (IsAuthenticated, )
+ serializer_class = TaskSerializer
+
+ def get_queryset(self):
+ return Task.objects.all()
+
+ def create(self, request, *args, **kwargs):
+ data = dict(request.data)
+ # TODO: Sure there is a better way to do this
+ o = Task.objects.create(
+ person_id = int(data['person_id'][0]),
+ coach_id = int(data['coach_id'][0]),
+ gym_id = int(data['gym_id'][0]),
+ station_id = int(data['station_id'][0]),
+ exercise_id = int(data['exercise_id'][0]),
+ type = data['type'][0],
+ load = float(data['load'][0]),
+ speed = float(data['speed'][0]),
+ percentMaxSpeed = data['percentMaxSpeed'][0],
+ laterality = data['laterality'][0],
+ sets = int(data['sets'][0]),
+ nreps = int(data['nreps'][0]),
+ lossBySpeed = int(data['lossBySpeed'][0]),
+ lossByPower = int(data['lossByPower'][0]),
+ comment = data['comment'][0]
+ )
+ s = TaskSerializer(o)
+ return Response(s.data)
+
+class TaskRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
+
+ permission_classes = (IsAuthenticated, )
+ serializer_class = TaskSerializer
+ lookup_field = 'task_id'
+
+ def retrieve(self, request, *args, **kwars):
+ #print(self.kwargs['task_id'])
+ o = get_object_or_404(Task, id=self.kwargs['task_id'])
+ s = TaskSerializer(o)
+ return Response(s.data)
diff --git a/chronojumpserver-django/chronojump_networks/tasks/apps.py
b/chronojumpserver-django/chronojump_networks/tasks/apps.py
new file mode 100644
index 0000000..226c1a4
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/tasks/apps.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class TasksConfig(AppConfig):
+ name = 'chronojump_networks.tasks'
diff --git a/chronojumpserver-django/chronojump_networks/tasks/migrations/0001_initial.py
b/chronojumpserver-django/chronojump_networks/tasks/migrations/0001_initial.py
new file mode 100644
index 0000000..381decf
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/tasks/migrations/0001_initial.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2018-04-01 21:53
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('organizations', '0005_auto_20180401_2146'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Task',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False,
verbose_name='ID')),
+ ('type', models.CharField(default='P', max_length=1)),
+ ('ts', models.DateTimeField(auto_now_add=True)),
+ ('person', models.ForeignKey(db_column='personId',
on_delete=django.db.models.deletion.CASCADE, to='organizations.Player')),
+ ],
+ options={
+ 'db_table': 'task',
+ },
+ ),
+ ]
diff --git a/chronojumpserver-django/chronojump_networks/tasks/migrations/0002_auto_20180401_2209.py
b/chronojumpserver-django/chronojump_networks/tasks/migrations/0002_auto_20180401_2209.py
new file mode 100644
index 0000000..2f55630
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/tasks/migrations/0002_auto_20180401_2209.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2018-04-01 22:09
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('organizations', '0005_auto_20180401_2146'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('tasks', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='task',
+ name='coach',
+ field=models.ForeignKey(db_column='coach_id', null=True,
on_delete=django.db.models.deletion.SET_NULL, related_name='coach_tasks', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='comment',
+ field=models.CharField(blank=True, max_length=150, null=True),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='done',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='exercise',
+ field=models.ForeignKey(db_column='exercise_id', null=True,
on_delete=django.db.models.deletion.CASCADE, related_name='exercise_tasks', to='organizations.Exercise'),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='gym',
+ field=models.ForeignKey(db_column='gym_id', null=True,
on_delete=django.db.models.deletion.CASCADE, related_name='gym_tasks', to='organizations.Gym'),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='laterality',
+ field=models.CharField(choices=[('RL', 'RL - both extremities'), ('R,L', 'R,L - First right,
then left'), ('R', 'R - Only right extremity'), ('L', 'L - Only left extremity')], default='RL',
max_length=3),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='lossByPower',
+ field=models.IntegerField(default=-1),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='lossBySpeed',
+ field=models.IntegerField(default=-1),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='nreps',
+ field=models.IntegerField(default=1),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='percentMaxSpeed',
+ field=models.FloatField(default=-1),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='sets',
+ field=models.IntegerField(default=1),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='speed',
+ field=models.FloatField(default=-1),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='station',
+ field=models.ForeignKey(db_column='station_id', null=True,
on_delete=django.db.models.deletion.CASCADE, related_name='station_tasks', to='organizations.Station'),
+ ),
+ migrations.AlterField(
+ model_name='task',
+ name='person',
+ field=models.ForeignKey(db_column='personId', on_delete=django.db.models.deletion.CASCADE,
related_name='player_tasks', to='organizations.Player'),
+ ),
+ ]
diff --git a/chronojumpserver-django/chronojump_networks/tasks/migrations/0003_task_load.py
b/chronojumpserver-django/chronojump_networks/tasks/migrations/0003_task_load.py
new file mode 100644
index 0000000..ad64d7d
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/tasks/migrations/0003_task_load.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.1 on 2018-04-02 10:26
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tasks', '0002_auto_20180401_2209'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='task',
+ name='load',
+ field=models.FloatField(default=-1),
+ ),
+ ]
diff --git a/chronojumpserver-django/chronojump_networks/tasks/migrations/__init__.py
b/chronojumpserver-django/chronojump_networks/tasks/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/chronojumpserver-django/chronojump_networks/tasks/models.py
b/chronojumpserver-django/chronojump_networks/tasks/models.py
new file mode 100644
index 0000000..290026e
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/tasks/models.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from chronojump_networks.organizations.models import Player, Gym, Station, Exercise, User
+
+# Create your models here.
+
+class Task(models.Model):
+ LATERALITY_BOTH = 'RL'
+ LATERALITY_FIRST_LEFT_THEN_RIGHT = 'R,L'
+ LATERALITY_LEFT = 'L'
+ LATERALITY_RIGHT = 'R'
+
+ LATERALITY_CHOICES= (
+ (LATERALITY_BOTH, _('RL - both extremities')),
+ (LATERALITY_FIRST_LEFT_THEN_RIGHT, _('R,L - First right, then left')),
+ (LATERALITY_RIGHT, _('R - Only right extremity')),
+ (LATERALITY_LEFT, _('L - Only left extremity'))
+ )
+
+ class Meta:
+ db_table = 'task'
+
+ type = models.CharField(max_length=1, default='P')
+ ts = models.DateTimeField(auto_now_add=True)
+ person = models.ForeignKey(Player,
+ on_delete = models.CASCADE,
+ db_column='personId',
+ related_name='player_tasks')
+ coach = models.ForeignKey(User,
+ on_delete = models.SET_NULL,
+ db_column = 'coach_id',
+ related_name = 'coach_tasks',
+ null=True)
+
+ gym = models.ForeignKey(Gym,
+ on_delete = models.CASCADE,
+ db_column='gym_id',
+ related_name='gym_tasks',
+ null=True)
+
+ station = models.ForeignKey(Station,
+ on_delete = models.CASCADE,
+ db_column='station_id',
+ related_name='station_tasks',
+ null=True)
+
+ exercise = models.ForeignKey(Exercise,
+ on_delete = models.CASCADE,
+ db_column='exercise_id',
+ related_name='exercise_tasks',
+ null=True)
+
+ sets = models.IntegerField(default = 1)
+ nreps = models.IntegerField(default = 1)
+ speed = models.FloatField(default = -1)
+ load = models.FloatField(default = -1)
+ percentMaxSpeed = models.FloatField(default = -1)
+ lossBySpeed = models.IntegerField(default = -1)
+ lossByPower = models.IntegerField(default = -1)
+ laterality = models.CharField(max_length = 3, choices = LATERALITY_CHOICES,
+ default=LATERALITY_BOTH )
+ comment = models.CharField(max_length=150, null=True, blank=True)
+ done = models.BooleanField(default=False)
diff --git a/chronojumpserver-django/chronojump_networks/tasks/tests.py
b/chronojumpserver-django/chronojump_networks/tasks/tests.py
new file mode 100644
index 0000000..5982e6b
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/tasks/tests.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/chronojumpserver-django/chronojump_networks/tasks/views.py
b/chronojumpserver-django/chronojump_networks/tasks/views.py
new file mode 100644
index 0000000..e784a0b
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/tasks/views.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/chronojumpserver-django/chronojump_networks/templates/admin/base_site.html
b/chronojumpserver-django/chronojump_networks/templates/admin/base_site.html
new file mode 100644
index 0000000..88d68d6
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/admin/base_site.html
@@ -0,0 +1,9 @@
+{% extends "admin/base.html" %}
+
+{% block title %}{{ title }} | Chronojump Site Admin{% endblock %}
+
+{% block branding %}
+<h1 id="site-name"><a href="{% url 'admin:index' %}">Chronojump Administration</a></h1>
+{% endblock %}
+
+{% block nav-global %}{% endblock %}
diff --git a/chronojumpserver-django/chronojump_networks/templates/base.html
b/chronojumpserver-django/chronojump_networks/templates/base.html
new file mode 100644
index 0000000..01b8e78
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/base.html
@@ -0,0 +1,39 @@
+{% load static i18n %}
+<!doctype html>
+{% get_current_language as LANGUAGE_CODE %}
+<html lang="{{ LANGUAGE_CODE }}">
+ <head>
+ <!-- Required meta tags -->
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+
+ <title>{% block title %}Welcome to Chronojump Networks{% endblock title %}</title>
+
+ {% block css %}
+
+ <!-- Bootstrap CSS -->
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
+ <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons"rel="stylesheet">
+ <link rel="stylesheet" href="{% static 'css/styles.css' %}">
+
+ {% endblock css %}
+
+ </head>
+ <body class="{% block body_class %}{% endblock %}">
+
+ {% block main %}
+ {% endblock main %}
+
+ {% block javascript %}
+
+ <!-- jQuery first, then Popper.js, then Bootstrap JS -->
+ <script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
crossorigin="anonymous"></script>
+ <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossorigin="anonymous"></script>
+
+ <script src="{% static 'js/chronojump.js' %}"></script>
+
+ {% endblock %}
+ </body>
+</html>
diff --git a/chronojumpserver-django/chronojump_networks/templates/layout.html
b/chronojumpserver-django/chronojump_networks/templates/layout.html
new file mode 100644
index 0000000..4376374
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/layout.html
@@ -0,0 +1,62 @@
+{% extends "base.html" %}
+{% load static i18n %}
+{% block main %}
+
+<nav class="navbar navbar-expand-lg navbar-dark" style="background-color:#0f2351">
+ <a class="navbar-brand" href="/"><img alt="Brand" src="{% static 'images/chronojump-logo.png' %}"
height="48px"></a>
+ <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggler"
aria-controls="navbarToggler" aria-expanded="false" aria-label="Toggle navigation">
+ <span class="navbar-toggler-icon"></span>
+ </button>
+ <div class="collapse navbar-collapse" id="navbarToggler">
+ <ul class="navbar-nav mr-auto">
+ <li class="nav-item active">
+ <a class="nav-link" href="/">{% trans 'Home' %} <span class="sr-only">(current)</span></a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link disabled" href="#">{% trans 'Results' %}</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link disabled" href="#">{% trans 'Sprints' %}</a>
+ </li>
+ {% if request.user.groups_by_coach %}
+ <li class="nav-item dropdown active">
+ <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownGroups" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
+ {% trans 'Groups' %}
+ </a>
+ <div class="dropdown-menu" aria-labelledby="navbarDropdownGroups">
+ <h6 class="dropdown-header">{% trans 'My groups' %}</h6>
+ {% for group in request.user.groups_by_coach.all %}
+ <a class="dropdown-item" href="{% url 'organizations:group_players_list'
organization_id=user.organization.id group_id=group.group.id %}">{{ group.group.name}}</a>
+ {% endfor %}
+ <div class="dropdown-divider"></div>
+ <a class="dropdown-item" href="#">{% trans 'Create a group' %}</a>
+ </div>
+ </li>
+ {% endif %}
+ {% if request.user.organization.gyms %}
+ <li class="nav-item dropdown active">
+ <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownGyms" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
+ {% trans 'Gyms' %}
+ </a>
+ <div class="dropdown-menu" aria-labelledby="navbarDropdownGroups">
+ <h6 class="dropdown-header">{% trans 'My gyms' %}</h6>
+ {% for gym in request.user.organization.gyms.all %}
+ <a class="dropdown-item" href="{% url 'organizations:gym_detail'
organization_id=user.organization.id gym_id=gym.id %}">{{ gym.name}}</a>
+ {% endfor %}
+ <div class="dropdown-divider"></div>
+ <a class="dropdown-item" href="#">{% trans 'Create a gym' %}</a>
+ </div>
+ </li>
+ {% endif %}
+ </ul>
+ <ul class="navbar-nav justify-content-end">
+ <li class="nav-item active"><a class="nav-link" href="#">{% trans 'Hello, '%}{{user.name}}</a></li>
+ <li class="nav-item active"><a class="nav-link" href="{% url 'logout' %}">{% trans 'Close session'
%}</a></li>
+ </ul>
+ </div>
+</nav>
+<div class="container-fluid">
+{% block content %}
+{% endblock content %}
+</div>
+{% endblock main %}
diff --git
a/chronojumpserver-django/chronojump_networks/templates/organizations/groups/group_players_list.html
b/chronojumpserver-django/chronojump_networks/templates/organizations/groups/group_players_list.html
new file mode 100644
index 0000000..327fc6a
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/organizations/groups/group_players_list.html
@@ -0,0 +1,603 @@
+{% extends 'layout.html' %}
+{% load static i18n %}
+
+{% block title %}Chronojump Networks | {{user.organization.name}} | {{group.name}}{% endblock %}
+
+{% block css %}
+{{ block.super }}
+<link rel="stylesheet" href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css">
+<link rel="stylesheet" href="https://cdn.datatables.net/buttons/1.5.1/css/buttons.bootstrap4.min.css">
+<!--<link rel="stylesheet"
href="https://cdn.datatables.net/fixedcolumns/3.2.4/css/fixedColumns.bootstrap4.min.css">-->
+
+{% endblock %}
+
+{% block content %}
+{% csrf_token %}
+
+<div class="page-header row">
+ <div class="col-sm-9">
+ <img src="/media/{{ user.organization.image }}" class="img-fluid float-left" width="48px" height="48px"
style="margin-top:12px;margin-right:10px;"/>
+ <h1 class="display-4">{{group.name}} <small class="text-muted" style="font-size:32px">{% trans 'Players
and tasks' %}</small></h1>
+ </div>
+ <div class="col-sm-3">
+ <dl class="row" style="margin-top:10px">
+ <dt class="col-sm-4 text-right">{% trans 'Responsible' %}:</dt>
+ <dd class="col-sm-8">{{ group.responsible.name }}</dd>
+ <dt class="col-sm-4 text-right">{% trans 'Gym' %}:</dt>
+ <dd class="col-sm-8">{{ group.gym }}</dd>
+ </dl>
+ </div>
+</div>
+
+<div class="row datatables_wrapped">
+ <div class="col">
+ <table id="players" cellspacing="0" cellpadding="0" class="table table-sm" style="width:100%">
+ </table>
+ </div>
+</div>
+
+
+<!-- Modal Form to add a player -->
+<div class="modal fade" id="addPlayerModal" tabindex="-1" role="dialog" aria-labelledby="{% trans 'Add
Player to Group' %}" aria-hidden="true">
+ <div class="modal-dialog modal-dialog-centered modal-lg" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title" id="addPlayersModalTitle">{% trans 'Add players to group '
%}{{group.name}}</h5>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <table id="add_players" cellspacing="0" class="table" style="width:100%">
+ </table>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{% trans 'Close'
%}</button>
+ <button type="button" class="btn btn-outline-primary disabled addPlayerButton">{% trans 'Add
players' %}</button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- Include Modal Task Form -->
+{% include 'organizations/groups/task_modal_form.html' %}
+
+<!-- Include Multiple Modal Task Form -->
+
+
+{% endblock %}
+
+{% block javascript %}
+{{ block.super }}
+<script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
+<script src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap4.min.js"></script>
+<script src="https://cdn.datatables.net/buttons/1.5.1/js/dataTables.buttons.min.js"></script>
+<script src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.bootstrap4.min.js"></script>
+<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.32/pdfmake.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.32/vfs_fonts.js"></script>
+<script src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.print.min.js"></script>-->
+<script src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.html5.min.js"></script>
+<!--<script src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.colVis.min.js"></script>
+<script src="https://cdn.datatables.net/fixedcolumns/3.2.4/js/dataTables.fixedColumns.min.js"></script>-->
+
+<script>
+ var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
+
+ $(document).ready(function() {
+ /* Datatable for players in groups */
+ var table_players = $('#players').DataTable({
+ lengthChange: false,
+ "ajax": {
+ "processing": true,
+ "url" : "{% url 'api_organizations:group_players_list' organization_id=user.organization.id
group_id=group.id %}",
+ "dataSrc": ""
+ },
+ {% if user.id == group.responsible.id %}
+ "order": [
+ [3, 'asc']
+ ],
+ {% else %}
+ "order": [
+ [2, 'asc']
+ ],
+ {% endif %}
+ "columns": [
+ {% if user.id == group.responsible.id %}
+ {
+ type: "html",
+ orderable:false,
+ render: function(value, type, row) {
+ return '<input class="selectPlayerCheckbox" type="checkbox" data-player-id="'
+ row.id + '"/>';
+ }
+ },
+ {% endif %}
+ { /* Column to show the tasks */
+ "className": 'details-control',
+ "orderable": false,
+ "data": null,
+ "defaultContent": ''
+ },
+ {
+ type: "html",
+ title: "",
+ data: 'image',
+ orderable: false,
+ render: function(value, type, row) {
+ if (value) {
+ var src = value;
+ } else {
+ // Otherwise show the no_image icon
+ var src = '/static/images/no_image.png';
+ }
+ var html = '<img src="' + src + '" class="img-fluid rounded" width="32px"
height="32px"/>';
+ return html;
+ }
+ },
+ {
+ "data": "number",
+ title: "{% trans 'Number' %}"
+ },
+ {
+ data: "name",
+ title: "{% trans 'Player name' %}",
+ },
+
+ {
+ "data": "height",
+ title: "{% trans 'Height' %}"
+ },
+ {
+ "data": "weight",
+ title: "{% trans 'Weight' %}"
+ },
+ {
+ "type": "html",
+ orderable: false,
+ render: function(value, type, row) {
+ var html = "";
+ // Single Task Button
+ html += '<button type="button" class="addTaskBtn btn btn-outline-success btn-sm" ';
+ html += 'data-player-id="' + row.id + '" ';
+ html += 'data-player-name="' + row.name + '" ';
+ html += 'data-toggle="tooltip" data-placement="right" ';
+ html += 'title="' + "{% trans 'Add new task to ' %}" + row.name + '">';
+ html += '<i class="material-icons" style="margin-top:3px;font-size:16px;">add</i></button>';
+ // Multiple Tasks
+ html += ' <button type="button" class="addMultipleTaskBtn btn btn-outline-warning btn-sm" ';
+ html += 'data-player-id="' + row.id + '" ';
+ html += 'data-player-name="' + row.name + '" ';
+ html += 'data-toggle="tooltip" data-placement="right" ';
+ html += 'title="' + "{% trans 'Add multiple tasks to ' %}" + row.name + '">';
+ html += '<i class="material-icons" style="margin-top:3px;font-size:16px;">plus_one</i></button>';
+ return html;
+ }
+ }
+ ],
+ "dom": "<'row'<'col-sm-6'B><'col-sm-6'f>>rtip",
+ buttons: [
+ {% if user.id == group.responsible.id %}
+ { /* Add Player button */
+ text: "{% trans 'Add players' %}",
+ className: "btn btn-primary",
+ action: function( e, dt, node, config ) {
+ $('#addPlayerModal').modal('show');
+
+ }
+ },
+ { /* Remove Players Button */
+ text: "{% trans 'Remove players' %}",
+ className: "btn btn-danger disabled removePlayerBtn",
+
+ action: function( e, dt, node, config ) {
+ var player_ids = [];
+ $.each($('.selectPlayerCheckbox:checked'), function(index, value) {
+ var v = $(value);
+ player_ids.push(v.attr('data-player-id'));
+
+ });
+ removePlayersFromGroup(player_ids);
+ }
+ }
+ {% endif %}
+ ],
+ initComplete: function() {
+ // Enable the remove player selection the first time data is loaded
+ enablePlayersTableFunctionality();
+ }
+ });
+
+ /* Task child rows */
+ function format ( d ) {
+ // `d` is the original data object for the row
+ if (d.player_tasks.length > 0) {
+ var html = '<table cellpadding="5" cellspacing="10" style="margin-left:50px;">';
+ html += "<thead><th></th><th>{% trans 'Gym' %}";
+ html += "<th>{% trans 'Station' %}</th><th>{% trans 'Exercise' %}</th>";
+ html += "<th>{% trans 'Sets' %}</th><th>{% trans 'Repeats' %}</th>";
+ html += "<th>{% trans 'Load' %}</th></th><th>{% trans 'Speed' %}";
+ html +="</th><th></th>"
+ $.each(d.player_tasks, function(index, task) {
+ console.log(task);
+ html += '<tbody><tr>';
+ html += '<td>' + (index + 1) + '</td>';
+ html += '<td>' + task.gym + '</td>';
+ html += '<td>' + task.station + '</td>';
+ html += '<td>' + task.exercise + '</td>';
+ html += '<td class="text-center">' + task.sets + '</td>';
+ html += '<td class="text-center">' + task.nreps + '</td>';
+ if (task.load >= 0) {
+ html += '<td class="text-center">' + task.load + '</td>';
+ } else {
+ html += '<td class="text-center no-value">-</td>';
+ }
+ if (task.speed >= 0) {
+ html += '<td class="text-center">' + task.speed + '</td>';
+ } else {
+ html += '<td class="text-center no-value">-</td>';
+ }
+
+ html += '<td>';
+ html += '<a class="task-link task-modify-link" data-toogle="tooltip" data-placement="right"
title="' + "{% trans 'Modify this task' %}" +'"';
+ html += 'data-task-id="' + task.id + '"'
+ html += 'data-task-type="' + task.type + '">'
+ html += '<i class="material-icons">mode_edit</i></a>';
+ html += ' ';
+ html += '<a class="task-link task-duplicate-link" data-toogle="tooltip" data-placement="right"
title="' + "{% trans 'Duplicate this task' %}" +'"';
+ html += 'data-task-id="' + task.id + '"'
+ html += 'data-task-type="' + task.type + '">'
+ html += '<i class="material-icons">content_copy</i></a>';
+ html += ' ';
+ html += '<a class="task-link task-delete-link" data-toogle="tooltip" data-placement="right"
title="' + "{% trans 'Delete this task' %}" +'"';
+ html += 'data-task-id="' + task.id + '"'
+ html += 'data-task-type="' + task.type + '">'
+ html += '<i class="material-icons">delete_sweep</i></a>';
+ html += '</td></tr>';
+ });
+ html += "</tbody></table>";
+
+ return html;
+ } else {
+ return '<h6 class="text-center font-weight-bold font-italic">' + "{% trans 'Player
has not tasks' %}" +'</h6>';
+ }
+ /*return '<table cellpadding="5" cellspacing="0" border="0" style="padding-left:50px;">'+
+ '<tr>'+
+ '<td>Full name:</td>'+
+ '<td>'+d.name+'</td>'+
+ '</tr>'+
+ '<tr>'+
+ '<td>Extension number:</td>'+
+ '<td>'+d.extn+'</td>'+
+ '</tr>'+
+ '<tr>'+
+ '<td>Extra info:</td>'+
+ '<td>And any further details here (images etc)...</td>'+
+ '</tr>'+
+ '</table>';*/
+ }
+ // Add event listener for opening and closing details
+ $('#players tbody').on('click', 'td.details-control', function () {
+ var tr = $(this).closest('tr');
+ var row = table_players.row( tr );
+
+ if ( row.child.isShown() ) {
+ // This row is already open - close it
+ row.child.hide();
+ tr.removeClass('shown');
+ }
+ else {
+ // Open this row
+ row.child( format(row.data()) ).show();
+ tr.addClass('shown');
+ }
+ } );
+
+ function enablePlayersTableFunctionality() {
+ // Enanble checkboxes
+ $('.removePlayerBtn').removeClass('disabled').addClass('disabled');
+ enableCheckboxSelection('selectPlayerCheckbox', 'removePlayerBtn');
+
+ // Enabling add task buttons
+ $('.addTaskBtn').on('click', function(){
+ var player = table_players.row($(this).parents('tr')).data();
+
+ // Set the title for new player task
+ var title = "{% trans 'Add new task to '%}" + player.name;
+ $('#taskModalTitle').text(title);
+
+ var player = table_players.row($(this).parents('tr')).data();
+ // newTaskOperation
+ newTaskInitialOperations(player.id);
+ $('#taskModalForm').modal('show');
+ });
+
+ }
+
+ /* Datatable for add players */
+ var add_players_table = $('#add_players').DataTable({
+ lengthChange: false,
+ "ajax": {
+ "processing": true,
+ "url" : "{% url 'api_organizations:group_add_players_list' organization_id=user.organization.id
group_id=group.id %}",
+ "dataSrc": ""
+ },
+ "columns": [
+ {
+ type: "html",
+ orderable:false,
+ render: function(value, type, row) {
+ return '<input class="addPlayerCheckbox" type="checkbox" data-player-id="' +
row.id + '"/>';
+ }
+ },
+ {
+ "data": "number",
+ title: "{% trans 'Number' %}"
+ },
+ {
+ data: "name",
+ title: "{% trans 'Player name' %}",
+ }
+ ],
+ "order": [
+ [1, 'asc']
+ ],
+ initComplete: function() {
+ /* Called when the table is loaded */
+ enableAddPlayersTableFunctionality();
+
+ // Set the action to add player button
+ $('.addPlayerButton').on('click', function(e) {
+ // Be sure to do something if button is enabled
+ if (!$('.addPlayerButton').hasClass('disabled')) {
+
+ var player_ids = [];
+
+ $.each($('.addPlayerCheckbox:checked'), function(index, value) {
+ var v = $(value);
+ player_ids.push(v.attr('data-player-id'));
+
+ });
+ // Call the function to add the players via ajax
+ addPlayersToGroup(player_ids);
+ e.preventDefault();
+ }
+ })
+ }
+ });
+
+
+
+ function enableAddPlayersTableFunctionality() {
+ $('.addPlayerButton').removeClass('disabled').addClass('disabled');
+ enableCheckboxSelection('addPlayerCheckbox', 'addPlayerButton');
+ }
+
+
+
+ /* Buttons operation */
+ function removePlayersFromGroup(player_ids) {
+ $.ajax({
+ 'url': "{% url 'api_organizations:remove_players_from_group' organization_id=user.organization.id
group_id=group.id %}",
+ 'method': 'DELETE',
+ 'data': {
+ 'player_ids': player_ids
+ },
+ success:function(data) {
+ console.log("players removed to the group");
+ // Hide AddPlayerModel dialog
+ var totalChecked = $('.selectPlayerCheckbox:checked').length;
+ if (totalChecked == 1) {
+ alert("{% trans 'The player has been removed from group' %}");
+ } else {
+ alert("{% trans 'The players have been removed from the group' %}");
+ }
+ table_players.ajax.reload(enablePlayersTableFunctionality, false);
+ add_players_table.ajax.reload(enableAddPlayersTableFunctionality, false);
+ }
+ });
+ }
+
+ /* Add the players selected */
+ function addPlayersToGroup(player_ids) {
+ $.ajax({
+ 'url': "{% url 'api_organizations:group_add_players_list' organization_id=user.organization.id
group_id=group.id %}",
+ 'method': 'POST',
+ 'data': {
+ 'player_ids': player_ids
+ },
+ success:function(data) {
+ // Hide AddPlayerModel dialog
+ var totalChecked = $('.addPlayerCheckbox:checked').length;
+ if (totalChecked == 1) {
+ alert("{% trans 'The player has been added to the group' %}");
+ } else {
+ alert("{% trans 'The players have been added to the group' %}");
+ }
+ table_players.ajax.reload(enablePlayersTableFunctionality, false);
+ add_players_table.ajax.reload(enableAddPlayersTableFunctionality, false);
+ $('#addPlayerModal').modal('hide');
+ },
+ error: function (request, status, error) {
+ alert(request.responseText);
+ }
+ });
+ }
+
+ /* Source for Tasks */
+
+ /* initials operations for task */
+ function newTaskInitialOperations(player_id) {
+ // Load station from assigned gym to group
+
+ console.log("initial gym is " + "{{ group.gym.name}}");
+ var group_id = {{ group.gym.id }};
+ console.log('Look for gym with id ' + {{ group.gym.id }});
+ $('#gymSelect').val(group_id);
+ // Set the player Id
+ $('#personId').val(player_id);
+ // Set taskId empty representing a new task
+ $('#taskId').val('');
+
+ loadGymStations(group_id);
+ }
+
+ $('#gymSelect').on('change', function() {
+ //Ajax call to retrieve all the stations. Call the function below.
+ var gym_id = $('#gymSelect').val();
+ // Show the stations of this gym
+ loadGymStations(gym_id);
+ });
+
+ /* Load stations from the gym into selectStation control */
+ function loadGymStations(gym_id) {
+ $('#stationSelect').find('option').remove().end();
+
+ $.ajax({
+ 'url': "{% url 'api_organizations:gym_stations' organization_id=user.organization.id %}",
+ data: {
+ 'gym_id': gym_id
+ },
+ success: function(data) {
+ console.log(data);
+ var count = 0;
+ $.each(data, function(i, item) {
+ count ++;
+ $('#stationSelect').append($('<option>', {
+ value: item.id,
+ text: item.name
+ }));
+ })
+ if (count > 0) {
+ $('#stationSelect').removeAttr('disabled');
+ // Load exercises from first station loaded
+ var station_id = $('#stationSelect option:first').val();
+ $('#stationSelect').val(station_id);
+ loadStationExercises(station_id);
+ } else {
+ // Disable task creation
+ }
+ }
+ });
+ }
+
+ $('#stationSelect').on('change', function() {
+ //Ajax call to retrieve all the exercises from a station
+ var station_id = $('#stationSelect').val();
+ // Show the exercises of this station
+ loadStationExercises(station_id);
+ });
+
+ /* Load stations from the gym into selectStation control */
+ function loadStationExercises(station_id) {
+ $('#exerciseSelect').find('option').remove().end();
+
+ $.ajax({
+ 'url': "{% url 'api_organizations:station_exercises' organization_id=user.organization.id %}",
+ data: {
+ 'station_id': station_id
+ },
+ success: function(data) {
+ console.log(data);
+ var count = 0;
+ $.each(data, function(i, item) {
+ count ++;
+ $('#exerciseSelect').append($('<option>', {
+ value: item.id,
+ text: item.name
+ }));
+ })
+ if (count > 0) {
+ $('#exerciseSelect').removeAttr('disabled');
+ // Load exercises from first station loaded
+ $('#exerciseSelect').val($('#exerciseSelect option:first').val());
+ } else {
+ // Disable task creation
+ }
+ }
+ });
+ }
+
+
+ $('#btnAddModifyTask').on('click', function(e) {
+ e.preventDefault();
+ var task = serializeTask();
+ putTask(task);
+ })
+
+ /* If value is empty set -1 as value */
+ function safe_task_value(selector) {
+
+ var val = $(selector).val();
+
+ if (val != '') {
+ return val;
+ }
+ return -1;
+ }
+
+ /* Get all the values in the form into json object */
+ function serializeTask() {
+ let gym_id = $('#gymSelect').val();
+ let station_id = $('#stationSelect').val();
+ let exercise_id = $('#exerciseSelect').val();
+ let person_id = $('#personId').val();
+ let sets = $('#numSets').val();
+ let nreps = $('#numReps').val();
+ let load = safe_task_value('#taskLoad');
+ var speed = safe_task_value('#taskSpeed');
+ let percentMaxSpeed = safe_task_value('#taskPercentMaxSpeed');
+ let laterality = $('#taskLaterality').val();
+ let comment = $('#taskCoachComment').val();
+ let lossBySpeed = safe_task_value('#taskLossBySpeed');
+ let lossByPower = safe_task_value('#taskLossByPower');
+ let coach_id = {{ user.id }};
+ var task = {
+ 'type' : 'S',
+ 'person_id': person_id,
+ 'gym_id': gym_id,
+ 'station_id': station_id,
+ 'exercise_id': exercise_id,
+ 'sets' : sets,
+ 'nreps' : nreps,
+ 'load' : load,
+ 'speed' : speed,
+ 'percentMaxSpeed' : percentMaxSpeed,
+ 'laterality': laterality,
+ 'comment': comment,
+ 'lossByPower': lossByPower,
+ 'lossBySpeed': lossBySpeed,
+ 'coach_id': 2
+ }
+ console.log(task);
+ return task;
+ }
+
+ function validateTask(task) {
+ return task;
+ }
+
+ function putTask(task) {
+ /* TODO: Add validation */
+ $.ajax({
+ 'url': "{% url 'api_tasks:tasks' %}",
+ 'method': 'POST',
+ 'data': task,
+ success: function(data) {
+ alert("The task has been added");
+ table_players.ajax.reload(enablePlayersTableFunctionality, false);
+ $('#taskModalForm').modal('hide');
+ }
+
+ })
+ }
+
+ // Enable tooltips
+ $(function () {
+ $('[data-toggle="tooltip"]').tooltip();
+ console.log("Enabling the tooltips");
+ })
+
+ }); /* End of Document ready */
+</script>
+
+{% endblock %}
diff --git a/chronojumpserver-django/chronojump_networks/templates/organizations/groups/task_modal_form.html
b/chronojumpserver-django/chronojump_networks/templates/organizations/groups/task_modal_form.html
new file mode 100644
index 0000000..e6494e2
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/organizations/groups/task_modal_form.html
@@ -0,0 +1,254 @@
+{% load static i18n %}
+
+<!-- Modal Form to add a new task -->
+<div id="taskModalForm" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="{% trans 'Add Task'
%}" aria-hidden="true">
+ <div class="modal-dialog modal-dialog-centered modal-lg" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title" id="taskModalTitle"></h5>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+
+ <input type="hidden" id="personId" />
+ <input type="hidden" id="taskId" />
+ <input type="hidden" id="coachId" value="{{ user.id }}"/>
+
+ <form>
+ <div class="form-row">
+ <div class="form-group col-sm-4">
+ <label for="gym">{% trans 'Gym' %}</label>
+ <select name="gym" class="form-control"
id="gymSelect">
+ {% for gym in user.organization.gyms.all %}
+ <option value={{gym.id}}>{{gym.name}}</option>
+ {% endfor %}
+ </select>
+ </div>
+ <div class="form-group col-sm-4">
+ <label for="station">{% trans 'Station' %}</label>
+ <select name="station" class="form-control"
id="stationSelect">
+ <!-- Load dynamically -->
+ </select>
+ </div>
+ <div class="form-group col-sm-4">
+ <label for="exercise">{% trans 'Exercise' %}</label>
+ <select name="exercise" class="form-control"
id="exerciseSelect">
+ <!-- Load dynamically -->
+ </select>
+ </div>
+ </div>
+ <hr>
+ <div class="form-row">
+ <div class="form-group col-sm-4">
+ <label for="numSets">{% trans '# Sets' %}</label>
+ <input id="numSets" name="numSets" type="number" class="form-control taskParameter" value="1"
min="1"></input>
+ </div>
+ <div class="form-group col-sm-4 ">
+ <label for="numReps">{% trans '# Repeats' %}</label>
+ <input id="numReps" name="numReps" type="number" class="form-control taskParameter" value="1"
min="1"></input>
+ </div>
+ <div class="form-group col-sm-4">
+ <label for="taskLoad">{% trans 'Load (Kg)' %}</label>
+ <input id="taskLoad" name="taskLoad" type="number" class="form-control taskParameter"></input>
+ </div>
+ </div>
+ <div class="form-row">
+ <div class="form-group col-sm-4">
+ <label for="taskMaxSpeed">{% trans 'Max Vm registered (m/s)' %}</label>
+ <input id="taskMaxSpeed" name="taskMaxSpeed" type="number" class="form-control taskParameter"
disabled readonly></input>
+ </div>
+ <div class="form-group col-sm-4 ">
+ <label for="taskPercentMax">{% trans 'Max Vm registered (%)' %}</label>
+ <input id="taskPercentMaxSpeed" name="taskPercentMaxSpeed" type="number" class="form-control"
disabled max="100" min="0"></input>
+ </div>
+ <div class="form-group col-sm-4">
+ <label for="taskSpeed">{% trans 'Speed (m/s)' %}</label>
+ <input id="taskSpeed" name="taskSpeed" type="number" class="form-control" min="-1" step="0.1"
disabled></input>
+ </div>
+ </div>
+ <div class="form-row">
+ <div class="form-group col-sm-4">
+ <label for="taskLossByPower">{% trans 'Loss by power' %}</label>
+ <input id="taskLossByPower" name="taskLossByPower" type="number" class="form-control"></input>
+ </div>
+ <div class="form-group col-sm-4">
+ <label for="taskLossBySpeed">{% trans 'Loss by speed' %}</label>
+ <input id="taskLossBySpeed" name="taskLossBySpeed" type="number" class="form-control"></input>
+ </div>
+ <div class="form-group col-sm-4">
+ <label for="taskLaterality">{% trans 'Laterality' %}</label>
+ <select class="form-control" id="taskLaterality" name="taskLaterality">
+ <option value="RL">{% trans 'RL - both extremities' %}</option>
+ <option value="R,L">{% trans 'R,L - First right, then left' %}</option>
+ <option value="R">{% trans 'R - Only right extremity' %}</option>
+ <option value="L">{% trans 'L - Only left extremity' %}</option>
+ </select>
+ </div>
+ </div>
+ <hr>
+ <div class="form-group">
+ <label for="taskCoachComment">{% trans 'Comments from coach' %}</label>
+ <textarea rows=2 class="form-control" id="taskCoachComment"></textarea>
+ </div>
+ </form>
+
+
+ </div> <!-- .modal-body -->
+ <div class="modal-footer">
+ <button type="button" class="btn btn-outline-secondary"
data-dismiss="modal">{% trans 'Cancel' %}</button>
+ <button id="btnAddModifyTask" type="button" class="btn btn-outline-primary" >{% trans 'Add task'
%}</button>
+ </div>
+ </div>
+ <!-- /.modal-content -->
+ </div>
+ <!-- /.modal-dialog -->
+</div>
+<!--
+<div class="tab-content">
+ <div role="tabpanel" id="singleTask" class="tab-pane fade in active">
+
+ <form class="form-horizontal" style="margin-top: 10px">
+
+ <label for="numReps" class="col-sm-2 control-label"># Repeticions:</label>
+ <div class="col-sm-2">
+ <input id="numReps" name="numReps" type="number" class="form-control" value="1" min="1"></input>
+ </div>
+ <label for="taskLoad" class="col-sm-2 control-label">Carrega (Kg):</label>
+ <div class="col-sm-2">
+ <input id="taskLoad" name="taskLoad" type="number" class="form-control" ></input>
+ </div>
+ </div>
+
+ <div class="form-group taskParameter">
+
+ <label for="taskMaxSpeed" class="col-sm-2 control-label" style="padding-top:0;margin-top:
-5px;"></label>
+ <div class="col-sm-2">
+ <input id="taskMaxSpeed" name="taskMaxSpeed" type="number" class="form-control" disabled
readonly></input>
+ </div>
+ <label for="taskpercentMax" class="col-sm-2 control-label" style="padding-top:0;margin-top:
-5px;">Max Vm<br>registrada (%):</label>
+ <div class="col-sm-2">
+ <input id="taskpercentMaxSpeed" name="taskpercentMaxSpeed" type="number" class="form-control"
disabled max="100" min="0"></input>
+ </div>
+ <label for="taskSpeed" class="col-sm-2 control-label">Velocitat (m/s):</label>
+ <div class="col-sm-2">
+ <input id="taskSpeed" name="taskSpeed" type="number" class="form-control" min="-1" step="0.1"
disabled></input>
+ </div>
+ </div>
+
+ <div class="form-group taskParameter">
+
+ <label for="taskLossByPower" class="col-sm-2 control-label" style="padding-top:0;margin-top:
-5px;">Perdua per potencia:</label>
+ <div class="col-sm-2">
+ <input id="taskLossByPower" name="taskLossByPower" type="number" class="form-control"></input>
+ </div>
+ <label for="taskLossBySpeed" class="col-sm-2 control-label" style="padding-top:0;margin-top:
-5px;">Perdua per velocitat:</label>
+ <div class="col-sm-2">
+ <input id="taskLossBySpeed" name="taskLossBySpeed" type="number" class="form-control"></input>
+ </div>
+ </div>
+
+ <div class="form-group taskParameter">
+ <label for="recipient-name" class="col-sm-2 control-label">Lateralitat:</label>
+ <div class="col-sm-10">
+ <select class="form-control" id="taskLaterality" name="taskLaterality">
+ <option value="RL">RL - ambdúes extremitats</option>
+ <option value="R,L">R,L - Primer amb la dreta, després amb l'esquerra</option>
+ <option value="R">R - Només extremitat dreta</option>
+ <option value="L">L - Només extremitat esquerra</option>
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="taskComment" class="control-label col-sm-2">Comentari de l'entrenador:</label>
+ <div class="col-sm-10">
+ <textarea rows=2 class="form-control" id="taskComment"></textarea>
+ </div>
+ </div>
+ </form>
+ </div>
+ <div role="tabpanel" id="macroTask" class="tab-pane fade">
+ <form class="form-horizontal" style="margin-top: 10px">
+ <div class="form-group">
+ <label for="numTasks" class="col-sm-2 control-label taskParameter macroTask"># Tasques:</label>
+ <div class="col-sm-2">
+ <input id="numTasks" name="numTaks" type="number" class="form-control taskParameter
macroTaskControl" value="1" min="1"></input>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="numSets2" class="col-sm-2 control-label taskParameter"># Series:</label>
+ <div class="col-sm-2">
+ <input id="numSets2" name="numSets2" type="number" class="form-control taskParameter
macroTaskControl" value="1" min="1"></input>
+ </div>
+ <label for="numReps2" class="col-sm-2 control-label"># Repeticions:</label>
+ <div class="col-sm-2">
+ <input id="numReps2" name="numReps2" type="number" class="form-control macroTaskControl" value="1"
min="1"></input>
+ </div>
+ <label for="taskLoad2" class="col-sm-2 control-label">Carrega (Kg):</label>
+ <div class="col-sm-2">
+ <input id="taskLoad2" name="taskLoad2" type="number" class="form-control macroTaskControl"
</input>
+ </div>
+ </div>
+ <div class="form-group taskParameter">
+
+ <label for="taskMaxSpeed2" class="col-sm-2 control-label" style="padding-top:0;margin-top:
-5px;">Max Vm<br>registrada (m/s):</label>
+ <div class="col-sm-2">
+ <input id="taskMaxSpeed2" name="taskMaxSpeed2" type="number" class="form-control" disabled
readonly></input>
+ </div>
+ <label for="taskpercentMaxSpeed2" class="col-sm-2 control-label" style="padding-top:0;margin-top:
-5px;">Max Vm<br>registrada (%):</label>
+ <div class="col-sm-2">
+ <input id="taskpercentMaxSpeed2" name="taskpercentMaxSpeed2" type="number" class="form-control
macroTaskControl" disabled max="100" min="0"></input>
+ </div>
+ <label for="taskSpeed2" class="col-sm-2 control-label">Velocitat (m/s):</label>
+ <div class="col-sm-2">
+ <input id="taskSpeed2" name="taskSpeed2" type="number" class="form-control macroTaskControl"
min="-1" step="0.1" disabled></input>
+ </div>
+ </div>
+ <div class="form-group taskParameter">
+
+ <label for="taskLossByPower2" class="col-sm-2 control-label" style="padding-top:0;margin-top:
-5px;">Perdua per potencia:</label>
+ <div class="col-sm-2">
+ <input id="taskLossByPower2" name="taskLossByPower2" type="number" class="form-control
macroTaskControl"></input>
+ </div>
+ <label for="taskLossBySpeed2" class="col-sm-2 control-label" style="padding-top:0;margin-top:
-5px;">Perdua per velocitat:</label>
+ <div class="col-sm-2">
+ <input id="taskLossBySpeed2" name="taskLossBySpeed2" type="number" class="form-control
macroTaskControl"></input>
+ </div>
+ </div>
+ <div class="form-group taskParameter">
+ <label for="recipient-name" class="col-sm-2 control-label">Lateralitat:</label>
+ <div class="col-sm-10">
+ <select class="form-control macroTaskControl" id="taskLaterality2" name="taskLaterality2">
+ <option value="RL">RL - ambdúes extremitats</option>
+ <option value="R,L">R,L - Primer amb la dreta, després amb l'esquerra</option>
+ <option value="R">R - Només extremitat dreta</option>
+ <option value="L">L - Només extremitat esquerra</option>
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-12">
+
+ </div>
+ </div>
+ <hr/>
+ <table class="table hide" id="tasksTable">
+ <caption>Pots modificar les tasques abans d'afegir-les.</caption>
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Carrega (Kg)</th>
+ <th>Max Vm registrada (%)</th>
+ <th>Velocitat (m/s)</th>
+ <th>Perdua per potencia</th>
+ <th>Perdua per velocitat</th>
+ </tr>
+ </thead>
+ <tbody>
+
+ </tbody>
+ </table>
+ </form>
+ </div>
+</div>-->
diff --git a/chronojumpserver-django/chronojump_networks/templates/organizations/gyms/gym_detail.html
b/chronojumpserver-django/chronojump_networks/templates/organizations/gyms/gym_detail.html
new file mode 100644
index 0000000..ef9ca7f
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/organizations/gyms/gym_detail.html
@@ -0,0 +1,193 @@
+{% extends 'layout.html' %}
+{% load static i18n %}
+
+{% block title %}Chronojump Networks | {{user.organization.name}} | {{group.name}}{% endblock %}
+
+{% block css %}
+{{ block.super }}
+<link rel="stylesheet" href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css">
+<link rel="stylesheet" href="https://cdn.datatables.net/buttons/1.5.1/css/buttons.bootstrap4.min.css">
+<!--<link rel="stylesheet"
href="https://cdn.datatables.net/fixedcolumns/3.2.4/css/fixedColumns.bootstrap4.min.css">-->
+
+{% endblock %}
+
+{% block content %}
+{% csrf_token %}
+
+<div class="page-header row">
+ <div class="col-sm-9">
+ <img src="/media/{{ user.organization.image }}" class="img-fluid float-left" width="48px" height="48px"
style="margin-top:12px;margin-right:10px;"/>
+ <h1 class="display-4">{{gym.name}} <small class="text-muted" style="font-size:32px">{% trans 'Stations
and exercises' %}</small></h1>
+ </div>
+ <div class="col-sm-3">
+ <dl class="row" style="margin-top:10px">
+ <dt class="col-sm-4 text-right">{% trans 'Responsible' %}:</dt>
+ <dd class="col-sm-8">{{ gym.responsible.name }}</dd>
+ </dl>
+ </div>
+</div>
+
+<div class="row datatables_wrapped">
+ <div class="col">
+ <table id="stations" cellspacing="0" cellpadding="0" class="table table-sm" style="width:100%">
+ </table>
+ </div>
+</div>
+
+
+
+{% endblock %}
+
+{% block javascript %}
+{{ block.super }}
+<script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
+<script src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap4.min.js"></script>
+<script src="https://cdn.datatables.net/buttons/1.5.1/js/dataTables.buttons.min.js"></script>
+<script src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.bootstrap4.min.js"></script>
+<script src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.html5.min.js"></script>
+
+<script>
+ var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
+
+ $(document).ready(function() {
+ /* Datatable for station and exercises */
+ var table_station = $('#stations').DataTable({
+ "scrollY": "600px",
+ "scrollCollapse": true,
+ "paging": false,
+ lengthChange: false,
+ "ajax": {
+ "processing": true,
+ "url" : "{% url 'api_organizations:gym_stations' organization_id=user.organization.id gym_id=gym.id
%}",
+ "dataSrc": ""
+ },
+ "columns": [
+ {
+ "data": "type",
+ title: "Type",
+ visible: false
+ },
+ {
+ type: "html",
+ orderable: false,
+ render: function(value, type, row) {
+ if (row.exercises.length > 0) {
+ return '<a href="#" class="text-dark show-hide-exercises" data-station-id="'+row.id+'"><i
class="material-icons">keyboard_arrow_down</i></a>';
+ } else {
+ return ' ';
+ }
+ },
+ width: 32,
+ className: "align-top"
+ },
+ {
+ "data": "name",
+ title: "{% trans 'Station name' %}",
+ width: 300,
+ className: "align-top"
+ },
+
+ {
+ "data": "exercises",
+ title: "{% trans 'Exercises' %}",
+ type: "html",
+ render: function(value, type, row) {
+ var html = "";
+ console.log(row);
+ if (value.length > 0) {
+ html = '<table class="collapse show" id="collapseExercises-' + row.id +'" style="width:100%">';
+ html += '<thead><th>{% trans "Exercise name" %}</th>';
+ html += '<th>{% trans "% Mass body displaced" %}</th>';
+ html += '</thead><tbody>';
+ $.each(value, function(index, exercise) {
+ html += '<tr><td>'+ exercise.name + '</td><td class="text-right">' +
exercise.percentBodyMassDisplaced +'</tr>';
+ });
+ html += '</tbody></table>';
+
+ } else {
+ html = '<h6 class="font-weight-bold font-italic">{% trans "Station has not exercises" %}</h6>';
+ }
+ return html;
+ }
+ },
+ {
+ type: 'html',
+ className: 'align-top',
+ render: function(value, type, row) {
+ var html = "";
+ // Single Task Button
+ html += '<button type="button" class="addTaskBtn btn btn-outline-success btn-sm" ';
+ html += 'data-player-id="' + row.id + '" ';
+ html += 'data-player-name="' + row.name + '" ';
+ html += 'data-toggle="tooltip" data-placement="right" ';
+ html += 'title="{% trans "Add new exercise" %}">';
+ html += '<i class="material-icons" style="margin-top:3px;font-size:16px;">add</i></button>';
+ // Multiple Tasks
+ html += ' <button type="button" class="addMultipleTaskBtn btn btn-outline-danger btn-sm" ';
+ html += 'data-player-id="' + row.id + '" ';
+ html += 'data-player-name="' + row.name + '" ';
+ html += 'data-toggle="tooltip" data-placement="right" ';
+ html += 'title="{% trans "Disable station" %}">';
+ html += '<i class="material-icons" style="margin-top:3px;font-size:16px;">block</i></button>';
+ return html;
+ }
+ }
+ ],
+ "drawCallback" : function ( settings ) {
+ var api = this.api();
+ var rows = api.rows( {page:'current'} ).nodes();
+ var last=null;
+
+ api.column(0, {page:'current'} ).data().each( function ( group, i ) {
+ if ( last !== group ) {
+ var group_name = "";
+ if (group == "I") {
+ group_name = "{% trans 'Inertial stations' %}";
+ } else if (group == "G") {
+ group_name = "{% trans 'Gravitatory stations' %}";
+ } else if (group == "S" ){
+ group_name = "{% trans 'Sprint stations' %}";
+ } else {
+ group_name = group;
+ }
+ $(rows).eq( i ).before(
+
+ '<tr class="group font-weight-bold"><td colspan="5">'+group_name+'</td></tr>'
+ );
+
+ last = group;
+ }
+ } );
+ },
+ "dom": "<'row'<'col-sm-6'B><'col-sm-6'f>>rtip",
+ buttons: [],
+ initComplete: function() {
+ // Enable table functionality
+ enableStationsTableFunctionality();
+ }
+ });
+
+ function enableStationsTableFunctionality() {
+ $('.show-hide-exercises').on('click', function(e) {
+ e.preventDefault();
+ let station_id = $(this).attr('data-station-id');
+ var t = $('#collapseExercises-' + station_id);
+ console.log(t);
+ if (t.hasClass('show')) {
+ // Collapse the table
+ t.collapse('hide');
+ // Change-icon
+ console.log($(this).children(0));
+ $(this).children(0).text = 'keyboard_arrow_up';
+ } else {
+ t.collapse('show');
+ // Change-icon
+ $(this).html = '<i class="material-icons">keyboard_arrow_down</i>';
+ }
+ });
+ }
+
+ }); /* End of Document ready */
+</script>
+
+{% endblock %}
diff --git a/chronojumpserver-django/chronojump_networks/templates/organizations/organization_detail.html
b/chronojumpserver-django/chronojump_networks/templates/organizations/organization_detail.html
new file mode 100644
index 0000000..1d412a8
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/organizations/organization_detail.html
@@ -0,0 +1,25 @@
+{% extends "layout.html" %}
+{% load static %}
+
+{% block title %}Organization: {{ object.name }}{% endblock %}
+
+{% block content %}
+<div class="container">
+
+ <div class="row">
+ <div class="col-sm-12">
+
+ <h2>{{ object.name }}</h2>
+ {% if object.image %}
+ <img src="/media/{{ object.image }}" alt="Logo Club"/>
+ {% endif %}
+ <ul>
+ {% for coach in object.coaches.all %}
+ <li>{{coach.name}}</li>
+ {% endfor %}
+ </ul>
+ </div>
+ </div>
+
+</div>
+{% endblock content %}
diff --git a/chronojumpserver-django/chronojump_networks/templates/organizations/organization_list.html
b/chronojumpserver-django/chronojump_networks/templates/organizations/organization_list.html
new file mode 100644
index 0000000..dbf1cf6
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/organizations/organization_list.html
@@ -0,0 +1,17 @@
+{% extends "base.html" %}
+{% load static i18n %}
+{% block title %}Members{% endblock %}
+
+{% block content %}
+<div class="container">
+ <h2>Organizations</h2>
+
+ <div class="list-group">
+ {% for organization in organization_list %}
+ <a href="{% url 'users:detail' organization.id %}" class="list-group-item">
+ <h4 class="list-group-item-heading">{{ organization.name }}</h4>
+ </a>
+ {% endfor %}
+ </div>
+</div>
+{% endblock content %}
diff --git a/chronojumpserver-django/chronojump_networks/templates/pages/403_csrf.html
b/chronojumpserver-django/chronojump_networks/templates/pages/403_csrf.html
new file mode 100644
index 0000000..77db8ae
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/pages/403_csrf.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+
+{% block title %}Forbidden (403){% endblock %}
+
+{% block content %}
+<h1>Forbidden (403)</h1>
+
+<p>CSRF verification failed. Request aborted.</p>
+{% endblock content %}
diff --git a/chronojumpserver-django/chronojump_networks/templates/pages/404.html
b/chronojumpserver-django/chronojump_networks/templates/pages/404.html
new file mode 100644
index 0000000..98327cd
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/pages/404.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+
+{% block title %}Page not found{% endblock %}
+
+{% block content %}
+<h1>Page not found</h1>
+
+<p>This is not the page you were looking for.</p>
+{% endblock content %}
diff --git a/chronojumpserver-django/chronojump_networks/templates/pages/500.html
b/chronojumpserver-django/chronojump_networks/templates/pages/500.html
new file mode 100644
index 0000000..21df606
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/pages/500.html
@@ -0,0 +1,13 @@
+{% extends "base.html" %}
+
+{% block title %}Server Error{% endblock %}
+
+{% block content %}
+<h1>Ooops!!! 500</h1>
+
+<h3>Looks like something went wrong!</h3>
+
+<p>We track these errors automatically, but if the problem persists feel free to contact us. In the
meantime, try refreshing.</p>
+{% endblock content %}
+
+
diff --git a/chronojumpserver-django/chronojump_networks/templates/pages/index.html
b/chronojumpserver-django/chronojump_networks/templates/pages/index.html
new file mode 100644
index 0000000..16afa16
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/pages/index.html
@@ -0,0 +1,121 @@
+{% extends "base.html" %}
+{% load static i18n %}
+
+{% block css %}
+
+{{ block.super }}
+<style>
+ .dashboard h2 {
+ margin-top: 10px;
+ }
+
+ .dashboard .buttons {
+ height: 100px;
+ }
+
+ .dashboard .buttons a {
+ height: 100px;
+ padding-top: 30px;
+ }
+</style>
+{% endblock %}
+
+{% block main %}
+<div class="container-fluid header-index">
+
+ <div class="row">
+ <div class="col-md-6" style="margin-top:40px">
+ <img class="img-responsive" src="{% static 'images/chronojump-logo.png' %}" >
+ </div>
+ <div class="col-md-6">
+ <div class="float-right">
+ <ul class="nav">
+ <li class="nav-item active"><a class="nav-link text-white" href="#">{% trans 'Welcome,
'%}{{user.name}}</a></li>
+ <li class="nav-item active"><a href="{% url 'logout' %}" class="nav-link text-white">{% trans
'Close session' %}</a></li>
+ </ul>
+ </div>
+ <div style="margin-top:60px">
+ <h1 class="text-center text-uppercase">Chronojump Server</h1>
+ <h4 class="text-center text-uppercase">No more blind training</h4>
+ </div>
+ </div>
+ </div>
+
+</div>
+<div class="container dashboard">
+{% if user.is_organization_member %}
+ <div class="row" style="margin-top:20px; border-bottom: 1px solid #000;padding-bottom:10px;">
+ <div class="col-md-4" >
+ <img src="/media/{{ user.organization.image }}" class="img-fluid" height="80px" />
+ </div>
+ <div class="col-md-8" >
+ <h1 class="display-4">{{ user.organization.name }}</h1>
+ </div>
+ </div>
+ <!-- Results -->
+ <h2 class="text-center">{% trans 'Player results' %}</h2>
+ <div class="row buttons">
+ <div class="col-sm-6">
+ <a class="btn btn-outline-dark btn-block font-weight-bold text-uppercase" href="#">{% trans
'Results' %}</a>
+ </div>
+ <div class="col-sm-6">
+ <a class="btn btn-outline-dark btn-block font-weight-bold text-uppercase" href="#">{% trans
'Sprints' %}</a>
+ </div>
+ </div>
+ {% if user.groups_by_coach %}
+ <h2 class="text-center">{% trans 'My Groups' %}</h2>
+ <div class="row buttons" >
+ {% for group in user.groups_by_coach.all %}
+ <div class="col-sm-6 col-md-4">
+ <a class="btn btn-outline-info btn-block font-weight-bold text-uppercase" href="{% url
'organizations:group_players_list' organization_id=user.organization.id group_id=group.group.id
%}">{{group.group.name}}</a>
+ </div>
+ {% endfor %}
+ </div>
+ {% endif %}
+
+ {% if user.organization.gyms %}
+ <h2 class="text-center">{% trans 'My Gyms' %}</h2>
+ <div class="row buttons" >
+ {% for gym in user.organization.gyms.all %}
+ <div class="col-sm-6 col-md-4">
+ <a class="btn btn-outline-warning btn-block font-weight-bold text-uppercase" href="{% url
'organizations:gym_detail' organization_id=user.organization.id gym_id=gym.id %}">{{gym.name}}</a>
+ </div>
+ {% endfor %}
+ </div>
+ {% endif %}
+
+ {% if user.id == user.organization.responsible.id %}
+ <h2 class="text-center">{% trans 'Organization options' %}</h2>
+ <div class="row buttons" >
+ <div class="col-sm-6 col-md-3">
+ <a class="btn btn-outline-primary btn-block font-weight-bold text-uppercase" href="">{% trans
'Staff' %}</a>
+ </div>
+ <div class="col-sm-6 col-md-3">
+ <a class="btn btn-outline-primary btn-block font-weight-bold text-uppercase" href="">{% trans
'Players' %}</a>
+ </div>
+ <div class="col-sm-6 col-md-3">
+ <a class="btn btn-outline-primary btn-block font-weight-bold text-uppercase" href="">{% trans
'Groups' %}</a>
+ </div>
+ <div class="col-sm-6 col-md-3">
+ <a class="btn btn-outline-primary btn-block font-weight-bold text-uppercase" href="">{% trans
'Gyms' %}</a>
+ </div>
+ </div>
+ {% endif %}
+
+{% else %}
+ <div class="row">
+ <div class="col-md-12">
+ <div class="alert alert-danger">{% trans 'You are not member of any organization' %}</div>
+ </div>
+ </div>
+{% endif %}
+ </div>
+</div>
+
+<nav class="navbar navbar-default fixed-bottom footer" style="height:40px">
+ <div class="container-fluid" style="margin-top: 10px">
+ <p class="navbar-text navbar-left">{% trans "Chronojump server is a product from Chronojump
Boscosystem ®" %}</p>
+ <p class="navbar-text navbar-right">{% trans "version 1.0" %}</p>
+ </div>
+</nav>
+{% endblock main %}
diff --git a/chronojumpserver-django/chronojump_networks/templates/users/login.html
b/chronojumpserver-django/chronojump_networks/templates/users/login.html
new file mode 100644
index 0000000..597e309
--- /dev/null
+++ b/chronojumpserver-django/chronojump_networks/templates/users/login.html
@@ -0,0 +1,31 @@
+{% extends 'base.html' %}
+{% load static i18n %}
+{% load crispy_forms_tags %}
+
+{% block body_class %}login{% endblock %}
+
+{% block main %}
+ <div class="container">
+ <div class="row">
+ <div class="offset-sm-3 col-sm-6" style="margin-top:50px;">
+ <div class="" style="background-color:#fff;;border-radius:15px;padding:10px">
+ <div style=" margin-bottom: 20px;background-color: #0f2351;padding:-10px">
+ <img class="img-responsive" src="{% static 'images/chronojump-logo.png' %}" >
+ </div>
+ {% if error_msg %}
+ <div class="alert alert-danger text-center">Error: {{error_msg}}</div>
+ {% endif %}
+ <form method="post" enctype="multipart/form-data">
+ <p class="text-center">{% trans "Enter your username and password" %}</p>
+ {% csrf_token %}
+ {{ form|crispy }}
+ {% if redirect_field_value %}
+ <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value
}}" />
+ {% endif %}
+ <button class="btn btn-primary btn-block" type="submit">{% trans "Login" %}</button>
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+{% endblock main %}
diff --git a/chronojumpserver-django/chronojump_networks/templates/users/user_list.html
b/chronojumpserver-django/chronojump_networks/templates/users/user_list.html
new file mode 100644
index 0000000..e69de29
diff --git a/chronojumpserver-django/config/__init__.py b/chronojumpserver-django/config/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/chronojumpserver-django/config/settings/__init__.py
b/chronojumpserver-django/config/settings/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/chronojumpserver-django/config/settings/base.py b/chronojumpserver-django/config/settings/base.py
new file mode 100644
index 0000000..587adf1
--- /dev/null
+++ b/chronojumpserver-django/config/settings/base.py
@@ -0,0 +1,239 @@
+"""
+Django settings for chronojump_networks project.
+
+Generated by 'django-admin startproject' using Django 1.11.11.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.11/ref/settings/
+"""
+
+
+import environ
+
+ROOT_DIR = environ.Path(__file__) - 3 # (cj1/config/settings/base.py - 3 = cj1/)
+APPS_DIR = ROOT_DIR.path('chronojump_networks')
+
+env = environ.Env()
+
+READ_DOT_ENV_FILE = env.bool('DJANGO_READ_DOT_ENV_FILE', default=False)
+if READ_DOT_ENV_FILE:
+ # OS environment variables take precedence over variables from .env
+ env.read_env(str(ROOT_DIR.path('.env')))
+
+# GENERAL
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#debug
+DEBUG = env.bool('DJANGO_DEBUG', False)
+# Local time zone. Choices are
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# though not all of them may be available with every OS.
+# In Windows, this must be set to your system time zone.
+TIME_ZONE = 'UTC'
+# https://docs.djangoproject.com/en/dev/ref/settings/#language-code
+
+# https://docs.djangoproject.com/en/dev/ref/settings/#site-id
+SITE_ID = 1
+# https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
+USE_I18N = True
+# https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
+USE_L10N = True
+# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
+USE_TZ = True
+
+from django.utils.translation import gettext_lazy as _
+
+LANGUAGE_CODE = 'en'
+
+LANGUAGES = (
+ ('en', _('English')),
+ ('ca', _('Catalan')),
+ ('es', _('Spanish')),
+ ('fr', _('French'))
+)
+
+LOCALE_PATHS = [
+ str(ROOT_DIR.path('locale'))
+]
+
+
+# DATABASES
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#databases
+DATABASES = {
+ 'default': env.db('DATABASE_URL'),
+}
+DATABASES['default']['ATOMIC_REQUESTS'] = True
+
+# URLS
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
+ROOT_URLCONF = 'config.urls'
+# https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
+WSGI_APPLICATION = 'config.wsgi.application'
+
+# APPS
+# ------------------------------------------------------------------------------
+DJANGO_APPS = [
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ # 'django.contrib.humanize', # Handy template tags
+ 'django.contrib.admin',
+]
+
+THIRD_PARTY_APPS = [
+ 'crispy_forms',
+ 'rest_framework',
+]
+
+LOCAL_APPS = [
+ 'chronojump_networks.organizations.apps.OrganizationsConfig',
+ 'chronojump_networks.tasks.apps.TasksConfig'
+
+]
+# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
+INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
+
+# MIGRATIONS
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#migration-modules
+MIGRATION_MODULES = {}
+
+# AUTHENTICATION
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends
+AUTHENTICATION_BACKENDS = [
+ 'django.contrib.auth.backends.ModelBackend',
+]
+# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model
+AUTH_USER_MODEL = 'organizations.User'
+# https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
+LOGIN_REDIRECT_URL = 'organization:users:redirect'
+# https://docs.djangoproject.com/en/dev/ref/settings/#login-url
+LOGIN_URL = 'login'
+
+# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+# MIDDLEWARE
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#middleware
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+# STATIC
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#static-root
+STATIC_ROOT = str(ROOT_DIR('staticfiles'))
+# https://docs.djangoproject.com/en/dev/ref/settings/#static-url
+STATIC_URL = '/static/'
+# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
+STATICFILES_DIRS = [
+ str(APPS_DIR.path('static')),
+]
+# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
+STATICFILES_FINDERS = [
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+]
+
+# MEDIA
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#media-root
+MEDIA_ROOT = str(APPS_DIR('media'))
+# https://docs.djangoproject.com/en/dev/ref/settings/#media-url
+MEDIA_URL = '/media/'
+
+# TEMPLATES
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#templates
+TEMPLATES = [
+ {
+ # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ # https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
+ 'DIRS': [
+ str(APPS_DIR.path('templates')),
+ ],
+ 'OPTIONS': {
+ # https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
+ 'debug': DEBUG,
+ # https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
+ # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
+ 'loaders': [
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+ ],
+ # https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.template.context_processors.i18n',
+ 'django.template.context_processors.media',
+ 'django.template.context_processors.static',
+ 'django.template.context_processors.tz',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+# http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs
+CRISPY_TEMPLATE_PACK = 'bootstrap4'
+
+# FIXTURES
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs
+FIXTURE_DIRS = (
+ str(APPS_DIR.path('fixtures')),
+)
+
+# ADMIN
+# ------------------------------------------------------------------------------
+# Django Admin URL regex.
+ADMIN_URL = r'^admin/'
+# https://docs.djangoproject.com/en/dev/ref/settings/#admins
+ADMINS = [
+ ("""Marcos Venteo""", 'mventeo gmail com'),
+]
+# https://docs.djangoproject.com/en/dev/ref/settings/#managers
+MANAGERS = ADMINS
+
+
+# Rest Framework
+# ------------------------------------------------------------------------------
+CSRF_USE_SESSIONS=True # Store the CSRF in Session is better than in a cookie
+
+REST_FRAMEWORK = {
+ 'DEFAULT_PERMISSION_CLASSES': (
+ 'rest_framework.permissions.IsAdminUser',
+ ),
+}
diff --git a/chronojumpserver-django/config/settings/local.py
b/chronojumpserver-django/config/settings/local.py
new file mode 100644
index 0000000..4c2c99d
--- /dev/null
+++ b/chronojumpserver-django/config/settings/local.py
@@ -0,0 +1,57 @@
+from .base import * # noqa
+from .base import env
+
+# GENERAL
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#debug
+DEBUG = env.bool('DJANGO_DEBUG', default=True)
+# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
+SECRET_KEY = env('DJANGO_SECRET_KEY',
default='frkS4wP89VDVwgwMutBoAnajychwzhxvcRYLCrtBbUjdVsas4lLba8tq9hUFfREU')
+# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
+ALLOWED_HOSTS = [
+ "localhost",
+ "0.0.0.0",
+ "127.0.0.1",
+]
+
+# CACHES
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#caches
+CACHES = {
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+ 'LOCATION': ''
+ }
+}
+
+# TEMPLATES
+# ------------------------------------------------------------------------------
+# https://docs.djangoproject.com/en/dev/ref/settings/#templates
+TEMPLATES[0]['OPTIONS']['debug'] = DEBUG # noqa F405
+
+
+# django-debug-toolbar
+# ------------------------------------------------------------------------------
+# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites
+INSTALLED_APPS += ['debug_toolbar'] # noqa F405
+# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware
+MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware'] # noqa F405
+# https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config
+DEBUG_TOOLBAR_CONFIG = {
+ 'DISABLE_PANELS': [
+ 'debug_toolbar.panels.redirects.RedirectsPanel',
+ ],
+ 'SHOW_TEMPLATE_CONTEXT': True,
+}
+
+INTERNAL_IPS = ['127.0.0.1', '10.0.2.2']
+import socket
+import os
+if os.environ.get('USE_DOCKER') == 'yes':
+ hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
+ INTERNAL_IPS += [ip[:-1] + '1' for ip in ips]
+
+# django-extensions
+# ------------------------------------------------------------------------------
+# https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration
+INSTALLED_APPS += ['django_extensions'] # noqa F405
diff --git a/chronojumpserver-django/config/urls.py b/chronojumpserver-django/config/urls.py
new file mode 100644
index 0000000..6c40dd5
--- /dev/null
+++ b/chronojumpserver-django/config/urls.py
@@ -0,0 +1,38 @@
+from django.conf import settings
+from django.conf.urls import url, include
+from django.conf.urls.static import static
+from django.contrib import admin
+from django.contrib.auth.decorators import login_required
+from django.views.generic import TemplateView
+from django.views import defaults as default_views
+
+from chronojump_networks.organizations import views
+
+urlpatterns = [
+ url(r'^$', login_required(TemplateView.as_view(template_name='pages/index.html')), name='index'),
+ url(settings.ADMIN_URL, admin.site.urls),
+ # User management
+ #url(r'^users/', include('chronojump_networks.users.urls', namespace='users')),
+ url(r'^organizations/', include('chronojump_networks.organizations.urls', namespace='organizations')),
+ url(r'^api/v1/organizations/', include('chronojump_networks.organizations.api.urls',
namespace='api_organizations')),
+ url(r'^api/v1/tasks/', include('chronojump_networks.tasks.api.urls', namespace='api_tasks')),
+ url(r'^login$', views.organization_login, name='login'),
+ url(r'^logout$', views.organization_logout, name='logout'),
+ url(r'^i18n/', include('django.conf.urls.i18n')),
+] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+
+
+if settings.DEBUG:
+ # This allows the error pages to be debugged during development, just visit
+ # these url in browser to see how these error pages look like.
+ urlpatterns += [
+ url(r'^400/$', default_views.bad_request, kwargs={'exception': Exception('Bad Request!')}),
+ url(r'^403/$', default_views.permission_denied, kwargs={'exception': Exception('Permission
Denied')}),
+ url(r'^404/$', default_views.page_not_found, kwargs={'exception': Exception('Page not Found')}),
+ url(r'^500/$', default_views.server_error),
+ ]
+ if 'debug_toolbar' in settings.INSTALLED_APPS:
+ import debug_toolbar
+ urlpatterns = [
+ url(r'^__debug__/', include(debug_toolbar.urls)),
+ ] + urlpatterns
diff --git a/chronojumpserver-django/config/wsgi.py b/chronojumpserver-django/config/wsgi.py
new file mode 100644
index 0000000..13524ce
--- /dev/null
+++ b/chronojumpserver-django/config/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for chronojump_networks project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chronojump_networks.settings")
+
+application = get_wsgi_application()
diff --git a/chronojumpserver-django/locale/es/LC_MESSAGES/django.po
b/chronojumpserver-django/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 0000000..3d2bceb
--- /dev/null
+++ b/chronojumpserver-django/locale/es/LC_MESSAGES/django.po
@@ -0,0 +1,335 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-04-02 09:09+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL li org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: chronojump_networks/organizations/api/permissions.py:5
+msgid "You are not a valid coach for this group. This will be notify"
+msgstr ""
+
+#: chronojump_networks/organizations/decorators.py:19
+msgid "The user does not belongs to this organization."
+msgstr ""
+
+#. Translators: Username for login form
+#: chronojump_networks/organizations/forms.py:14
+msgid "Username"
+msgstr "Usuario"
+
+#. Translators: Password for login form
+#: chronojump_networks/organizations/forms.py:21
+msgid "Password"
+msgstr "Contraseña"
+
+#: chronojump_networks/organizations/models.py:25 config/settings/base.py:51
+msgid "Spanish"
+msgstr ""
+
+#: chronojump_networks/organizations/models.py:26 config/settings/base.py:50
+msgid "Catalan"
+msgstr ""
+
+#: chronojump_networks/organizations/models.py:27 config/settings/base.py:52
+msgid "French"
+msgstr ""
+
+#: chronojump_networks/organizations/models.py:28 config/settings/base.py:49
+msgid "English"
+msgstr ""
+
+#: chronojump_networks/organizations/models.py:36
+msgid "Name of User"
+msgstr ""
+
+#: chronojump_networks/organizations/models.py:99
+msgid "Coach"
+msgstr ""
+
+#: chronojump_networks/organizations/models.py:100
+msgid "Coach Lead"
+msgstr ""
+
+#: chronojump_networks/organizations/models.py:152
+msgid "Sprint"
+msgstr ""
+
+#: chronojump_networks/organizations/models.py:153
+msgid "Inertial"
+msgstr ""
+
+#: chronojump_networks/organizations/models.py:154
+msgid "Gravitatory"
+msgstr ""
+
+#: chronojump_networks/organizations/views.py:80
+msgid "Invalid username or password"
+msgstr ""
+
+#: chronojump_networks/tasks/models.py:18
+#: chronojump_networks/templates/organizations/task_modal_form.html:82
+msgid "RL - both extremities"
+msgstr ""
+
+#: chronojump_networks/tasks/models.py:19
+#: chronojump_networks/templates/organizations/task_modal_form.html:83
+msgid "R,L - First right, then left"
+msgstr ""
+
+#: chronojump_networks/tasks/models.py:20
+#: chronojump_networks/templates/organizations/task_modal_form.html:84
+msgid "R - Only right extremity"
+msgstr ""
+
+#: chronojump_networks/tasks/models.py:21
+#: chronojump_networks/templates/organizations/task_modal_form.html:85
+msgid "L - Only left extremity"
+msgstr ""
+
+#: chronojump_networks/templates/layout.html:13
+msgid "Home"
+msgstr ""
+
+#: chronojump_networks/templates/layout.html:16
+#: chronojump_networks/templates/pages/index.html:59
+msgid "Results"
+msgstr ""
+
+#: chronojump_networks/templates/layout.html:19
+#: chronojump_networks/templates/pages/index.html:62
+msgid "Sprints"
+msgstr ""
+
+#: chronojump_networks/templates/layout.html:22
+msgid "Tasks"
+msgstr "Tareas"
+
+#: chronojump_networks/templates/layout.html:27
+#: chronojump_networks/templates/pages/index.html:97
+msgid "Groups"
+msgstr "Grupos"
+
+#: chronojump_networks/templates/layout.html:30
+msgid "My groups"
+msgstr "Mis grupos"
+
+#: chronojump_networks/templates/layout.html:35
+msgid "Create a group"
+msgstr "Crear nuevo grupo"
+
+#: chronojump_networks/templates/layout.html:41
+msgid "Hello, "
+msgstr "Hola, "
+
+#: chronojump_networks/templates/layout.html:42
+#: chronojump_networks/templates/pages/index.html:34
+msgid "Close session"
+msgstr "Cerrar sesión"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:20
+msgid "Players and tasks"
+msgstr "Jugadores y tareas"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:24
+msgid "Responsible"
+msgstr "Responsable"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:26
+#: chronojump_networks/templates/organizations/task_modal_form.html:21
+msgid "Gym"
+msgstr "Gimnasio"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:41
+msgid "Add Player to Group"
+msgstr "Añadir jugadores al grupo"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:45
+msgid "Add players to group "
+msgstr "Añadir jugadores al grupo"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:55
+msgid "Close"
+msgstr "Cerrar"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:56
+#: chronojump_networks/templates/organizations/group_players_list.html:175
+msgid "Add players"
+msgstr "Añadir jugadores"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:133
+#: chronojump_networks/templates/organizations/group_players_list.html:241
+msgid "Number"
+msgstr "Dorsal"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:137
+#: chronojump_networks/templates/organizations/group_players_list.html:245
+#, fuzzy
+#| msgid "Username"
+msgid "Player name"
+msgstr "Usuario"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:142
+msgid "Height"
+msgstr "Altura"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:146
+msgid "Weight"
+msgstr "Peso"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:158
+#: chronojump_networks/templates/organizations/group_players_list.html:214
+msgid "Add new task to "
+msgstr "Añadir nueva tarea a "
+
+#: chronojump_networks/templates/organizations/group_players_list.html:165
+msgid "Add multiple tasks to "
+msgstr "Añadir múltiple tareas a "
+
+#: chronojump_networks/templates/organizations/group_players_list.html:183
+msgid "Remove players"
+msgstr "Quitar jugadores"
+
+#: chronojump_networks/templates/organizations/group_players_list.html:297
+msgid "The player has been removed from group"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/group_players_list.html:299
+msgid "The players have been removed from the group"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/group_players_list.html:319
+msgid "The player has been added to the group"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/group_players_list.html:321
+msgid "The players have been added to the group"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:4
+#, fuzzy
+#| msgid "Tasks"
+msgid "Add Task"
+msgstr "Tareas"
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:29
+msgid "Station"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:35
+msgid "Exercise"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:44
+msgid "# Sets"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:48
+msgid "# Repeats"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:52
+msgid "Load (Kg)"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:58
+msgid "Max Vm registered (m/s)"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:62
+#, python-format
+msgid "Max Vm registered (%%)"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:66
+msgid "Speed (m/s)"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:72
+msgid "Loss by power"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:76
+msgid "Loss by speed"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:80
+msgid "Laterality"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:91
+msgid "Comments from coach"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:99
+msgid "Cancel"
+msgstr ""
+
+#: chronojump_networks/templates/organizations/task_modal_form.html:100
+msgid "Add task"
+msgstr ""
+
+#: chronojump_networks/templates/pages/index.html:33
+msgid "Welcome, "
+msgstr ""
+
+#: chronojump_networks/templates/pages/index.html:56
+msgid "Player results"
+msgstr "Resultado de jugadores"
+
+#: chronojump_networks/templates/pages/index.html:66
+msgid "My Groups"
+msgstr "Mis grupos"
+
+#: chronojump_networks/templates/pages/index.html:77
+msgid "My Gyms"
+msgstr "Mis gimnasios"
+
+#: chronojump_networks/templates/pages/index.html:88
+msgid "Organization options"
+msgstr "Opciones de la organización"
+
+#: chronojump_networks/templates/pages/index.html:91
+msgid "Staff"
+msgstr "Miembros"
+
+#: chronojump_networks/templates/pages/index.html:94
+msgid "Players"
+msgstr "Jugadores"
+
+#: chronojump_networks/templates/pages/index.html:100
+msgid "Gyms"
+msgstr ""
+
+#: chronojump_networks/templates/pages/index.html:108
+msgid "You are not member of any organization"
+msgstr ""
+
+#: chronojump_networks/templates/pages/index.html:116
+msgid "Chronojump server is a product from Chronojump Boscosystem ®"
+msgstr ""
+
+#: chronojump_networks/templates/pages/index.html:117
+msgid "version 1.0"
+msgstr ""
+
+#: chronojump_networks/templates/users/login.html:19
+msgid "Enter your username and password"
+msgstr "Introduce tu usuario y contraseña"
+
+#: chronojump_networks/templates/users/login.html:25
+msgid "Login"
+msgstr "Acceder"
diff --git a/chronojumpserver-django/manage.py b/chronojumpserver-django/manage.py
new file mode 100755
index 0000000..4ab9300
--- /dev/null
+++ b/chronojumpserver-django/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError:
+ # The above import may fail for some other reason. Ensure that the
+ # issue is really that Django is missing to avoid masking other
+ # exceptions on Python 2.
+ try:
+ import django
+ except ImportError:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ )
+ raise
+ execute_from_command_line(sys.argv)
diff --git a/chronojumpserver/__init__.py b/chronojumpserver/__init__.py
index 7424f46..19a6ab1 100755
--- a/chronojumpserver/__init__.py
+++ b/chronojumpserver/__init__.py
@@ -7,7 +7,8 @@ import click
from flask import Flask, send_from_directory
from flask_login import LoginManager, current_user
from flask_babel import Babel, refresh, gettext, request
-
+from flask_admin import Admin
+from flask_admin.contrib.sqla import ModelView
import ConfigParser
import os
@@ -19,6 +20,8 @@ config.read('/etc/chronojump.conf')
app = Flask(__name__)
+# Administration
+admin = Admin(app, name='Chronojump Server Administration', template_mode='bootstrap3')
login_manager = LoginManager()
login_manager.init_app(app)
@@ -98,7 +101,7 @@ def shutdown_session(exception=None):
from chronojumpserver.database import init_db
init_db()
-from chronojumpserver.models import User, Language
+from chronojumpserver.models import User, Language, Person
# To retrieve the user from session
@login_manager.user_loader
def load_user(user_id):
@@ -124,3 +127,8 @@ def generate_string(eval_ctx, localized_value):
return ""
else:
return Markup("\"" + localized_value + "\"").unescape()
+
+
+# Administration models
+admin.add_view(ModelView(User, db_session))
+admin.add_view(ModelView(Person, db_session))
diff --git a/chronojumpserver/models.py b/chronojumpserver/models.py
index dd4c876..3def127 100755
--- a/chronojumpserver/models.py
+++ b/chronojumpserver/models.py
@@ -348,7 +348,7 @@ class Task(Base):
def __repr__(self):
"""Representation of the task."""
# For the moment show only task
- return '<Task %r>' % self.description
+ return '<Task %r>' % ""
class ResultEncoder(Base):
diff --git a/chronojumpserver/templates/groups_and_players.html
b/chronojumpserver/templates/groups_and_players.html
new file mode 100644
index 0000000..8d24bef
--- /dev/null
+++ b/chronojumpserver/templates/groups_and_players.html
@@ -0,0 +1,19 @@
+{% extends 'layout.html' %}
+{% block head %} {{ super() }}
+<link href="{{ url_for('assets', filename='DataTables/media/css/dataTables.bootstrap.min.css') }}"
rel="stylesheet" />
+{% endblock %}
+
+{% block content %}
+
+<div class="page-header">
+ <h1>{{_('Groups and Players')}}</h1>
+</div>
+
+<!-- /.modal -->
+{% endblock %}
+
+{% block script %} {{ super() }}
+<script src="{{ url_for('assets', filename='DataTables/media/js/jquery.dataTables.min.js') }}"></script>
+<script src="{{ url_for('assets', filename='DataTables/media/js/dataTables.bootstrap.min.js') }}"></script>
+
+{% endblock %}
diff --git a/chronojumpserver/views.py b/chronojumpserver/views.py
index b21fd1f..27c6120 100755
--- a/chronojumpserver/views.py
+++ b/chronojumpserver/views.py
@@ -268,3 +268,10 @@ def login():
def logout():
logout_user()
return redirect(url_for('login'))
+
+
+# Networks changes
+@app.route("/group-players")
+@login_required
+def show_groups_and_players():
+ return render_template('groups_and_players.html')
diff --git a/compose/local/django/start.sh b/compose/local/django/start.sh
new file mode 100644
index 0000000..69210e8
--- /dev/null
+++ b/compose/local/django/start.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+python manage.py migrate
+python manage.py compilemessages
+python manage.py runserver 0.0.0.0:8000
diff --git a/compose/local/flask/chronojump.conf.template b/compose/local/flask/chronojump.conf.template
new file mode 100644
index 0000000..4c6d0f8
--- /dev/null
+++ b/compose/local/flask/chronojump.conf.template
@@ -0,0 +1,20 @@
+[api]
+debug=True
+port=8080
+version=1.2.3
+backtraces_directory=/tmp
+chronojump_stable_version=1.2.3
+photos_dir=/source/chronojumpserver/static/images/photos
+mono_executables_dir=/source/chronojumpserver/rfid-csharp
+
+[db]
+server=db
+name=chronojump
+user=chronojump
+password=chronojump
+
+[club]
+name=FC Barcelona
+
+[security]
+secret_key=Thisisaweakexampleofsecretkey
diff --git a/compose/local/flask/start.sh b/compose/local/flask/start.sh
new file mode 100644
index 0000000..1d9a4ce
--- /dev/null
+++ b/compose/local/flask/start.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+flask run --host 0.0.0.0
diff --git a/compose/production/django/entrypoint.sh b/compose/production/django/entrypoint.sh
new file mode 100644
index 0000000..476e89b
--- /dev/null
+++ b/compose/production/django/entrypoint.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+
+cmd="$@"
+
+export DATABASE_URL="mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@${MYSQL_HOSTNAME}:3306/${MYSQL_DATABASE}"
+
+exec $cmd
diff --git a/local.yml b/local.yml
new file mode 100755
index 0000000..18436af
--- /dev/null
+++ b/local.yml
@@ -0,0 +1,50 @@
+version: '2'
+
+services:
+ db:
+ image: mysql:5.7
+ volumes:
+ - db_data:/var/lib/mysql
+ env_file:
+ - ./.envs/.local/.mysql
+
+ mysql_django:
+ image: mysql:5.7
+ volumes:
+ - db_data_django:/var/lib/mysql
+ env_file:
+ - ./.envs/.local/.mysql-django
+
+ django:
+ build:
+ context: .
+ dockerfile: ./compose/local/django/Dockerfile
+ volumes:
+ - ./chronojumpserver-django:/app
+ depends_on:
+ - mysql_django
+ ports:
+ - "8000:8000"
+ env_file:
+ - ./.envs/.local/.django
+ - ./.envs/.local/.mysql-django
+ command: /start.sh
+ flask:
+ build:
+ context: .
+ dockerfile: ./compose/local/flask/Dockerfile
+ volumes:
+ - .:/source
+ depends_on:
+ - db
+ ports:
+ - "5000:5000"
+ env_file:
+ - ./.envs/.local/.flask
+ - ./.envs/.local/.mysql
+ command: /start.sh
+
+
+volumes:
+ db_data:
+ db_data_django:
diff --git a/requirements/base.txt b/requirements/base.txt
new file mode 100644
index 0000000..37eaf23
--- /dev/null
+++ b/requirements/base.txt
@@ -0,0 +1,26 @@
+aniso8601==1.2.1
+appdirs==1.4.3
+click==6.7
+itsdangerous==0.24
+Jinja2==2.7.3
+MarkupSafe==0.23
+mysql==0.0.1
+MySQL-python==1.2.5
+packaging==16.8
+PyMySQL==0.7.11
+pyparsing==2.2.0
+python-dateutil==2.6.0
+pytz==2017.2
+six==1.10.0
+SQLAlchemy==1.1.10
+Werkzeug==0.9.6
+
+
+# Flask
+# ------------------------------------------------------------------------------
+Flask==0.12.2
+Flask-Autodoc==0.1.1
+Flask-SQLAlchemy==2.2
+Flask-WTF==0.14.2
+flask-login
+Flask-Babel
diff --git a/requirements/django/base.txt b/requirements/django/base.txt
new file mode 100644
index 0000000..167b052
--- /dev/null
+++ b/requirements/django/base.txt
@@ -0,0 +1,18 @@
+environ==1.0
+MySQL-python==1.2.5
+pytz==2018.3 # https://github.com/stub42/pytz
+awesome-slugify==1.6.5 # https://github.com/dimka665/awesome-slugify
+Pillow==5.0.0 # https://github.com/python-pillow/Pillow
+argon2-cffi==18.1.0 # https://github.com/hynek/argon2_cffi
+
+# Django
+# ------------------------------------------------------------------------------
+django==1.11.1 # https://www.djangoproject.com/
+django-environ==0.4.4 # https://github.com/joke2k/django-environ
+django-model-utils==3.1.1 # https://github.com/jazzband/django-model-utils
+django-crispy-forms==1.7.2 # https://github.com/django-crispy-forms/django-crispy-forms
+django-redis==4.9.0 # https://github.com/niwinz/django-redis
+
+# Django REST Framework
+djangorestframework==3.7.7 # https://github.com/encode/django-rest-framework
+coreapi==2.3.3 # https://github.com/core-api/python-client
diff --git a/requirements/django/local.txt b/requirements/django/local.txt
new file mode 100644
index 0000000..c724814
--- /dev/null
+++ b/requirements/django/local.txt
@@ -0,0 +1,25 @@
+-r ./base.txt
+
+Werkzeug==0.14.1 # https://github.com/pallets/werkzeug
+ipdb==0.11 # https://github.com/gotcha/ipdb
+Sphinx==1.7.2 # https://github.com/sphinx-doc/sphinx
+
+# Testing
+# ------------------------------------------------------------------------------
+pytest==3.5.0 # https://github.com/pytest-dev/pytest
+pytest-sugar==0.9.1 # https://github.com/Frozenball/pytest-sugar
+
+# Code quality
+# ------------------------------------------------------------------------------
+flake8==3.5.0 # https://github.com/PyCQA/flake8
+coverage==4.5.1 # https://github.com/nedbat/coveragepy
+
+# Django
+# ------------------------------------------------------------------------------
+factory-boy==2.10.0 # https://github.com/FactoryBoy/factory_boy
+django-test-plus==1.0.22 # https://github.com/revsys/django-test-plus
+
+django-debug-toolbar==1.9.1 # https://github.com/jazzband/django-debug-toolbar
+django-extensions==2.0.6 # https://github.com/django-extensions/django-extensions
+django-coverage-plugin==1.5.0 # https://github.com/nedbat/django_coverage_plugin
+pytest-django==3.1.2 # https://github.com/pytest-dev/pytest-django
diff --git a/requirements/local.txt b/requirements/local.txt
new file mode 100644
index 0000000..4953a6c
--- /dev/null
+++ b/requirements/local.txt
@@ -0,0 +1,5 @@
+-r ./base.txt
+
+# Flask-Admin
+#------------------------------------------------------------------------------
+Flask-Admin==1.5.0
diff --git a/requirements/production.txt b/requirements/production.txt
new file mode 100644
index 0000000..db167e2
--- /dev/null
+++ b/requirements/production.txt
@@ -0,0 +1,4 @@
+-r ./base.txt
+
+virtualenv==15.1.0
+gunicorn==0.14.5
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]