Drop API, do a mobile website

This commit is contained in:
Gabriel Augendre 2018-03-03 18:16:27 +01:00
parent 3e4c70237f
commit b80c7c0319
No known key found for this signature in database
GPG key ID: F360212F958357D4
19 changed files with 492 additions and 72 deletions

View file

@ -9,6 +9,7 @@ name = "pypi"
django = "*" django = "*"
djangorestframework = "*" djangorestframework = "*"
"django-bootstrap4" = "*"
[dev-packages] [dev-packages]

8
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "1f3d4cc7027911c0d5599450f18f080ccf209827c665920bad7fc6e832bb3b38" "sha256": "c3ee9bacf90669123493994e4f194606cd953134dd819c73684629335c5a7ee6"
}, },
"host-environment-markers": { "host-environment-markers": {
"implementation_name": "cpython", "implementation_name": "cpython",
@ -34,6 +34,12 @@
], ],
"version": "==2.0.2" "version": "==2.0.2"
}, },
"django-bootstrap4": {
"hashes": [
"sha256:6db4a27b33851833e68b96344f9df063150dcace8d4787ebfc21eceb55196945"
],
"version": "==0.0.6"
},
"djangorestframework": { "djangorestframework": {
"hashes": [ "hashes": [
"sha256:1f6baf40ed456ed2af6bd1a4ff8bbc3503cebea16509993aea2b7085bc097766", "sha256:1f6baf40ed456ed2af6bd1a4ff8bbc3503cebea16509993aea2b7085bc097766",

View file

@ -1,3 +1,28 @@
from django.contrib import admin from django.contrib import admin
# Register your models here. from gym.models import Room, Equipment, Setting, TheoreticalMax, Session, Round
@admin.register(Room)
class RoomAdmin(admin.ModelAdmin):
pass
@admin.register(Equipment)
class EquipmentAdmin(admin.ModelAdmin):
pass
@admin.register(TheoreticalMax)
class TheoreticalMaxAdmin(admin.ModelAdmin):
pass
@admin.register(Session)
class SessionAdmin(admin.ModelAdmin):
pass
@admin.register(Round)
class RoundAdmin(admin.ModelAdmin):
pass

View file

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-03-03 17:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gym', '0002_auto_20180303_1201'),
]
operations = [
migrations.AlterField(
model_name='session',
name='date',
field=models.DateTimeField(verbose_name='date et heure de début'),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-03-03 17:01
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('gym', '0003_auto_20180303_1801'),
]
operations = [
migrations.RenameField(
model_name='session',
old_name='date',
new_name='start',
),
]

View file

@ -11,6 +11,9 @@ class Room(models.Model):
longitude = models.DecimalField('longitude', max_digits=11, decimal_places=8, blank=True) longitude = models.DecimalField('longitude', max_digits=11, decimal_places=8, blank=True)
notes = models.TextField('notes', blank=True) notes = models.TextField('notes', blank=True)
def __str__(self):
return self.name
class Equipment(models.Model): class Equipment(models.Model):
class Meta: class Meta:
@ -26,6 +29,13 @@ class Equipment(models.Model):
null=True null=True
) )
@property
def last_theoretical_max(self):
return self.theoretical_maxs.order_by('-start').first()
def __str__(self):
return f'{self.name} ({self.room.name})'
class Setting(models.Model): class Setting(models.Model):
class Meta: class Meta:
@ -41,6 +51,9 @@ class Setting(models.Model):
name = models.CharField('nom', max_length=200) name = models.CharField('nom', max_length=200)
value = models.CharField('valeur', max_length=200) value = models.CharField('valeur', max_length=200)
def __str__(self):
return f'{self.name}={self.value}'
class TheoreticalMax(models.Model): class TheoreticalMax(models.Model):
class Meta: class Meta:
@ -56,13 +69,16 @@ class TheoreticalMax(models.Model):
date = models.DateField('date') date = models.DateField('date')
value = models.FloatField('valeur') value = models.FloatField('valeur')
def __str__(self):
return f'{self.value}kg le {self.date}'
class Session(models.Model): class Session(models.Model):
class Meta: class Meta:
verbose_name = 'séance' verbose_name = 'séance'
verbose_name_plural = 'séances' verbose_name_plural = 'séances'
date = models.DateField('date') start = models.DateTimeField('date et heure de début')
room = models.ForeignKey( room = models.ForeignKey(
verbose_name='salle', verbose_name='salle',
to=Room, to=Room,

View file

@ -1,22 +0,0 @@
from django.contrib.auth.models import User
from rest_framework import serializers
from gym.models import Room, Equipment
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'first_name', 'last_name')
class RoomSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Room
fields = ('url', 'name', 'latitude', 'longitude', 'notes', 'equipments')
class EquipmentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Equipment
fields = ('url', 'name', 'room')

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
{% block add-head %}
{% endblock %}
<title>Gym &centerdot; {% block title %}Home{% endblock %}</title>
</head>
<body>
<div class="container">
{% for message in messages %}
<div class="alert alert-dismissible alert-{{ message.tags }} fade show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endfor %}
{% block content %}
{% endblock %}
</div>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
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>
</body>
</html>

View file

@ -0,0 +1,26 @@
{% extends 'gym/base.html' %}
{% block content %}
<div class="row">
<div class="col-12">
<h1>{% block title %}{{ equipment.name }} ({{ equipment.room.name }}){% endblock %}</h1>
</div>
</div>
<div class="row">
<div class="col-12">
<a href="{% url 'room-detail' equipment.room.pk %}" class="btn btn-secondary">Retourner à la salle</a>
<a href="{% url 'setting-create' %}?equipment={{ equipment.pk }}" class="btn btn-success">Ajouter un réglage</a>
</div>
</div>
<div class="row">
<div class="col-12">
<h2>Réglages</h2>
<ul class="list-group">
{% for setting in equipment.settings.all %}
<a href="{% url 'setting-edit' setting.pk %}" class="list-group-item">{{ setting.name }}
: {{ setting.value }}</a>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,22 @@
{% extends 'gym/base.html' %}
{% load bootstrap4 %}
{% block content %}
<div class="row">
<div class="col-12">
<h1>{% block title %}Ajouter une machine{% endblock %}</h1>
</div>
</div>
<div class="row">
<div class="col-12">
<form action="" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Valider</button>
{% endbuttons %}
</form>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,38 @@
{% extends 'gym/base.html' %}
{% block content %}
<div class="row">
<div class="col-12">
<h1>{% block title %}{{ room.name }}{% endblock %}</h1>
</div>
</div>
<div class="row">
<div class="col-12">
<a href="{% url 'rooms-list' %}" class="btn btn-secondary">Liste des salles</a>
<a href="{% url 'equipment-create' %}?room={{ room.pk }}" class="btn btn-success">Ajouter une machine</a>
<a href="{% url 'session-start' %}?room={{ room.pk }}" class="btn btn-primary">Commencer une séance</a>
</div>
</div>
<div class="row">
<div class="col-12">
<h2>Machines</h2>
<ul class="list-group">
{% for equipment in room.equipments.all %}
<a href="{% url 'equipment-detail' equipment.pk %}" class="list-group-item">{{ equipment.name }}</a>
{% endfor %}
</ul>
</div>
</div>
{% if room.sessions.all %}
<div class="row">
<div class="col-12">
<h2>Séances</h2>
<ul class="list-group">
{% for session in room.sessions.all %}
<a href="{% url 'session-detail' session.pk %}" class="list-group-item">{{ session.start }}</a>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,18 @@
{% extends 'gym/base.html' %}
{% block content %}
<div class="row">
<div class="col-12">
<h1>{% block title %}Salles{% endblock %}</h1>
</div>
</div>
<div class="row">
<div class="col-12">
<ul class="list-group">
{% for room in rooms %}
<a href="{% url 'room-detail' room.pk %}" class="list-group-item">{{ room.name }}</a>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,42 @@
{% extends 'gym/base.html' %}
{% load bootstrap4 %}
{% block content %}
<div class="row">
<div class="col-12">
<h1>{% block title %}Séance en cours{% endblock %}</h1>
</div>
</div>
<div class="row">
<div class="col-12">
<a href="{% url 'room-detail' session.room.pk %}" class="btn btn-secondary">Retourner à la salle</a>
<a href="{% url 'equipment-create' %}?room={{ room.pk }}" class="btn btn-success">Ajouter une machine</a>
</div>
</div>
<div class="row">
<div class="col-12">
<h2>Infos</h2>
</div>
</div>
<div class="row">
<div class="col-12">
<ul>
<li>{{ session.room.name }}</li>
<li>{{ session.start }}</li>
{% if session.notes %}
<li>{{ session.notes }}</li>
{% endif %}
</ul>
</div>
</div>
<div class="row">
<div class="col-12">
<h2>Machines</h2>
<ul class="list-group">
{% for equipment in session.room.equipments.all %}
<a href="{% url 'equipment-detail' equipment.pk %}" class="list-group-item">{{ equipment.name }}</a>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,29 @@
{% extends 'gym/base.html' %}
{% load bootstrap4 %}
{% block content %}
<div class="row">
<div class="col-12">
<h1>{% block title %}{{ title }} une séance{% endblock %}</h1>
</div>
</div>
<div class="row">
<div class="col-12">
{% if room %}
<a href="{% url 'room-detail' room.pk %}" class="btn btn-secondary">Retourner à la salle</a>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-12">
<form action="" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Valider</button>
{% endbuttons %}
</form>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,19 @@
{% extends 'gym/base.html' %}
{% load bootstrap4 %}
{% block content %}
<div class="row">
<div class="col-12">
<h1>{% block title %}Supprimer un réglage{% endblock %}</h1>
</div>
</div>
<div class="row">
<div class="col-12">
<form action="" method="post">
{% csrf_token %}
<p>Êtes-vous sûr de vouloir supprimer le réglage <code>{{ setting }}</code> pour la machine "{{ setting.equipment }}" ?</p>
<input class="btn btn-danger" type="submit" value="Confirmer"/>
</form>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,31 @@
{% extends 'gym/base.html' %}
{% load bootstrap4 %}
{% block content %}
<div class="row">
<div class="col-12">
<h1>{% block title %}{{ title }} un réglage{% endblock %}</h1>
</div>
</div>
<div class="row">
<div class="col-12">
{% if equipment %}
<a href="{% url 'equipment-detail' equipment.pk %}" class="btn btn-secondary">Retourner à la machine</a>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-12">
<form action="" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-success">Valider</button>
{% if edit %}
<a href="{% url 'setting-delete' setting.pk %}" class="btn btn-danger">Supprimer</a>
{% endif %}
{% endbuttons %}
</form>
</div>
</div>
{% endblock %}

View file

@ -1,21 +1,19 @@
from django.conf.urls import url, include from django.urls import path
from rest_framework import routers
from . import views
router = routers.DefaultRouter() from . import views
router.register(r'rooms', views.RoomViewSet)
router.register(r'equipments', views.EquipmentViewSet)
# Wire up our API using automatic URL routing. # Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API. # Additionally, we include login URLs for the browsable API.
urlpatterns = [ urlpatterns = [
url(r'^api/', include(router.urls)), path('', views.RoomListView.as_view(), name='rooms-list'),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), path('rooms/<int:pk>/', views.RoomDetailView.as_view(), name='room-detail'),
url(r'^api/me/', views.MeView.as_view()) path('equipment/add/', views.EquipmentCreateView.as_view(), name='equipment-create'),
path('equipment/<int:pk>/', views.EquipmentDetailView.as_view(), name='equipment-detail'),
path('setting/add/', views.SettingCreateView.as_view(), name='setting-create'),
path('setting/<int:pk>/', views.SettingUpdateView.as_view(), name='setting-edit'),
path('setting/<int:pk>/delete', views.SettingDeleteView.as_view(), name='setting-delete'),
path('session/start/', views.SessionCreateView.as_view(), name='session-start'),
path('session/<int:pk>/', views.SessionDetailView.as_view(), name='session-detail'),
# path('session/<int:pk>/delete', views.SessionDeleteView.as_view(), name='session-delete'),
] ]
from rest_framework.authtoken import views
urlpatterns += [
url(r'^api-token-auth/', views.obtain_auth_token)
]

View file

@ -1,24 +1,135 @@
from django.contrib.auth.models import User import datetime
from rest_framework import viewsets
from rest_framework.generics import RetrieveUpdateAPIView
from gym.models import Room, Equipment from django.shortcuts import get_object_or_404
from gym.serializers import RoomSerializer, EquipmentSerializer, UserSerializer from django.urls import reverse
from django.views import generic
from gym.models import Room, Equipment, Setting, Session
class MeView(RetrieveUpdateAPIView): class RoomListView(generic.ListView):
serializer_class = UserSerializer
queryset = User.objects.none()
def get_object(self):
return self.request.user
class RoomViewSet(viewsets.ModelViewSet):
queryset = Room.objects.all().order_by('name') queryset = Room.objects.all().order_by('name')
serializer_class = RoomSerializer context_object_name = 'rooms'
template_name = 'gym/rooms.html'
class EquipmentViewSet(viewsets.ModelViewSet): class RoomDetailView(generic.DetailView):
queryset = Equipment.objects.all().order_by('name') model = Room
serializer_class = EquipmentSerializer context_object_name = 'room'
template_name = 'gym/room.html'
class EquipmentDetailView(generic.DetailView):
model = Equipment
context_object_name = 'equipment'
template_name = 'gym/equipment.html'
class EquipmentCreateView(generic.CreateView):
model = Equipment
fields = ['room', 'name']
template_name = 'gym/equipment_edit.html'
room = None
def get_success_url(self):
return reverse('equipment-detail', kwargs={'pk': self.object.pk})
def dispatch(self, request, *args, **kwargs):
default_room_id = self.request.GET.get('room')
if default_room_id:
self.room = get_object_or_404(Room, pk=default_room_id)
return super().dispatch(request, *args, **kwargs)
def get_initial(self):
initial = super().get_initial()
initial['room'] = self.room
return initial
class SettingCreateView(generic.CreateView):
model = Setting
fields = ['equipment', 'name', 'value']
template_name = 'gym/setting_edit.html'
equipment = None
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Ajouter'
context['equipment'] = self.equipment
return context
def dispatch(self, request, *args, **kwargs):
default_equipment_id = self.request.GET.get('equipment')
if default_equipment_id:
self.equipment = get_object_or_404(Equipment, pk=default_equipment_id)
return super().dispatch(request, *args, **kwargs)
def get_initial(self):
initial = super().get_initial()
initial['equipment'] = self.equipment
return initial
def get_success_url(self):
return reverse('equipment-detail', kwargs={'pk': self.object.equipment.pk})
class SettingUpdateView(generic.UpdateView):
model = Setting
fields = ['equipment', 'name', 'value']
template_name = 'gym/setting_edit.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['edit'] = True
context['title'] = 'Modifier'
context['equipment'] = self.object.equipment
return context
def get_success_url(self):
return reverse('equipment-detail', kwargs={'pk': self.object.equipment.pk})
class SettingDeleteView(generic.DeleteView):
model = Setting
template_name = 'gym/setting_confirm_delete.html'
context_object_name = 'setting'
def get_success_url(self):
return reverse('equipment-detail', kwargs={'pk': self.object.equipment.pk})
class SessionCreateView(generic.CreateView):
model = Session
fields = ['room', 'start']
template_name = 'gym/session_edit.html'
room = None
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Démarrer'
context['room'] = self.room
return context
def get_success_url(self):
return reverse('session-detail', kwargs={'pk': self.object.pk})
def dispatch(self, request, *args, **kwargs):
self.room = get_object_or_404(Room, pk=self.request.GET.get('room'))
return super().dispatch(request, *args, **kwargs)
def get_initial(self):
initial = super().get_initial()
initial['room'] = self.room
initial['start'] = datetime.datetime.now()
return initial
class SessionDetailView(generic.DetailView):
model = Session
context_object_name = 'session'
template_name = 'gym/session_detail.html'

View file

@ -24,7 +24,7 @@ SECRET_KEY = 'x*8q7sd14&a%cu95$h@jl&&#bb&j(j*-6h5!3atz*v%!zo3hd4'
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = ['*']
# Application definition # Application definition
@ -36,8 +36,7 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'gym', 'gym',
'rest_framework', 'bootstrap4',
'rest_framework.authtoken',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -98,18 +97,6 @@ AUTH_PASSWORD_VALIDATORS = [
}, },
] ]
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissions'
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/ # https://docs.djangoproject.com/en/2.0/topics/i18n/