Merge pull request 'own_events' (#9) from own_events into main
continuous-integration/drone/push Build is passing Details

Reviewed-on: #9
This commit is contained in:
jens 2021-08-06 22:13:51 +00:00
commit ec3102bdcd
26 changed files with 449 additions and 94 deletions

23
.drone.yml Normal file
View File

@ -0,0 +1,23 @@
kind: pipeline
type: exec
name: default
platform:
os: linux
arch: amd64
steps:
- name: install deps
commands:
- dnf install -y python3 python3-pip
- pip3 install -U Django coverage flake8 pylint django-coverage-plugin pylint-django django-bootstrap4
- name: run unittests
commands:
- coverage run --source='.' manage.py test --noinput --parallel
- name: run flake8
commands:
- flake8 --max-line-length=120
- name: run pylint
commands:
- DJANGO_SETTINGS_MODULE=nmgfitness.settings pylint --rcfile=.pylintrc -- **/*.py

15
.pylintrc Normal file
View File

@ -0,0 +1,15 @@
[MASTER]
load-plugins=pylint_django
django-settings-module=nmgfitness.settings
ignore-paths=.*/migrations
[FORMAT]
max-line-length=120
[MESSAGES CONTROL]
[DESIGN]
max-parents=13
[TYPECHECK]
generated-members=REQUEST,acl_users,aq_parent,"[a-zA-Z]+_set{1,2}",save,delete

1
README.md Normal file
View File

@ -0,0 +1 @@
[![Build Status](https://drone.caret.be/api/badges/jens/nmgfitness/status.svg)](https://drone.caret.be/jens/nmgfitness)

View File

@ -3,18 +3,12 @@
import os import os
import sys import sys
from django.core.management import execute_from_command_line
def main(): def main():
"""Run administrative tasks.""" """Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nmgfitness.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nmgfitness.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
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?"
) from exc
execute_from_command_line(sys.argv) execute_from_command_line(sys.argv)

View File

@ -1,3 +1,4 @@
"""Admin module"""
from django.apps import apps from django.apps import apps
from django.contrib import admin from django.contrib import admin

79
nmgfitness/base.html Normal file
View File

@ -0,0 +1,79 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<title>NMG Fitness Registration</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="/">Calendar</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'logout' %}">Logout {{ user.username }}</a>
<li class="nav-item">
<a class="nav-link" href="{% url 'password_change' %} ">Change Password</a>
</li>
{% if user.is_superuser %}
<li class="nav-item">
<a class="nav-link" href="{% url 'admin:index' %} ">Admin</a>
</li>
{% endif %}
{% else %}
<a class="nav-link" href="{% url 'login' %}">Login</a>
{% endif %}
</li>
<!--li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Dropdown
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#">Disabled</a>
</li!-->
</ul>
</div>
</nav>
<div class="container">
<div class="row justify-content-center">
{% block content %}
BASE TEMPLATE
{% endblock %}
</div>
</div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.js"></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

@ -1,13 +1,7 @@
<html> {% extends 'base.html' %}
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.css"/> {% block content %}
<link rel="stylesheet" <script>
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.js"></script>
<script>
function mycolor(str){ function mycolor(str){
var hash = 0; var hash = 0;
for (var i = 0; i < str.length; i++) { for (var i = 0; i < str.length; i++) {
@ -36,40 +30,26 @@
center: 'title', center: 'title',
right: 'month,agendaWeek,agendaDay' right: 'month,agendaWeek,agendaDay'
}, },
events: [ events: '/all_events',
{% for event in events %}
{
title: "{{ event.name}}",
start: '{{ event.start|date:"Y-m-d H:i" }}',
end: '{{ event.end|date:"Y-m-d H:i" }}',
id: '{{ event.id }}',
backgroundColor: mycolor('{{ event.name }}'),
},
{% endfor %}
],
selectable: true, selectable: true,
selectHelper: true, selectHelper: true,
editable: true, editable: true,
eventLimit: true, eventLimit: true,
select: function (start, end, allDay) { select: function (start, end, allDay) {
var title = prompt("Enter Your Name"); var start = $.fullCalendar.formatDate(start, "Y-MM-DD HH:mm:ss");
if (title) { var end = $.fullCalendar.formatDate(end, "Y-MM-DD HH:mm:ss");
var start = $.fullCalendar.formatDate(start, "Y-MM-DD HH:mm:ss"); $.ajax({
var end = $.fullCalendar.formatDate(end, "Y-MM-DD HH:mm:ss"); type: "GET",
$.ajax({ url: '/add_event',
type: "GET", data: {'title': '{{ user }}', 'start': start, 'end': end},
url: '/add_event', dataType: "json",
data: {'title': title, 'start': start, 'end': end}, success: function (data) {
dataType: "json", calendar.fullCalendar('refetchEvents');
success: function (data) { },
//alert("Added Successfully"); failure: function (data) {
location.reload(); alert('There is a problem!!!');
}, }
failure: function (data) { });
alert('There is a problem!!!');
}
});
}
}, },
eventResize: function (event) { eventResize: function (event) {
var start = $.fullCalendar.formatDate(event.start, "Y-MM-DD HH:mm:ss"); var start = $.fullCalendar.formatDate(event.start, "Y-MM-DD HH:mm:ss");
@ -82,15 +62,15 @@
data: {'title': title, 'start': start, 'end': end, 'id': id}, data: {'title': title, 'start': start, 'end': end, 'id': id},
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
//alert('Event Update'); calendar.fullCalendar('refetchEvents');
}, },
failure: function (data) { error: function (data) {
alert('There is a problem!!!'); calendar.fullCalendar('refetchEvents');
} },
}); });
}, },
eventAfterRender: function (event, element, view)
eventAfterRender: function(event, element, view)
{ {
$(element).css('width','20%'); $(element).css('width','20%');
}, },
@ -107,8 +87,8 @@
success: function (data) { success: function (data) {
//alert('Event Update'); //alert('Event Update');
}, },
failure: function (data) { error: function (data) {
alert('There is a problem!!!'); calendar.fullCalendar('refetchEvents');
} }
}); });
}, },
@ -123,10 +103,10 @@
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
//alert('Event Removed'); //alert('Event Removed');
location.reload(); calendar.fullCalendar('refetchEvents');
}, },
failure: function (data) { error: function (data) {
alert('There is a problem!!!'); alert('Could not remove');
} }
}); });
} }
@ -136,12 +116,9 @@
}); });
</script> </script>
</head>
<body>
<br/> <br/>
<h2 align="center"><a href="#">Nieuwe Molens Gent -- Fitness calendar</a></h2> <h2 align="center"><a href="#">Nieuwe Molens Gent -- Fitness calendar</a></h2>
<br/> <br/>
<div class="container">
<div id="calendar"></div> <div id="calendar"></div>
<p> <p>
Klik op de gewenste plaats in de kalender om een nieuwe reservatie toe te voegen, vul je naam in in het dialoogvenster. Klik op de gewenste plaats in de kalender om een nieuwe reservatie toe te voegen, vul je naam in in het dialoogvenster.
@ -152,10 +129,6 @@
<p> <p>
Je kan een event verschuiven of verlengen/verkorten door het vast te nemen en te verschuiven. Je kan een event verschuiven of verlengen/verkorten door het vast te nemen en te verschuiven.
</p> </p>
<p>
Indien het niet lukt om op een reeds gerserveerd moment een nieuwe reservatie aan te maken,
</p>
</div> </div>
</body> {% endblock %}
</html>

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.6 on 2021-08-05 21:57
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('nmgfitness', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='events',
name='name',
),
migrations.AddField(
model_name='events',
name='user',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='auth.user'),
preserve_default=False,
),
]

View File

@ -1,11 +1,20 @@
"""Nmgfitness Events model"""
from django.db import models from django.db import models
from django.conf import settings
class Events(models.Model): class Events(models.Model):
"""
The events class
The main class used in this project
Events have an id (Autofield primary key)
Start and stop time
and are linked to a user
"""
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255,null=True,blank=True) start = models.DateTimeField(null=True, blank=True)
start = models.DateTimeField(null=True,blank=True) end = models.DateTimeField(null=True, blank=True)
end = models.DateTimeField(null=True,blank=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return self.name return self.user.username

View File

@ -27,17 +27,20 @@ DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
LOGIN_URL = '/accounts/login/'
LOGIN_REDIRECT_URL = '/'
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', 'users.apps.UsersConfig',
'nmgfitness',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.admin',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'nmgfitness', 'bootstrap4',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -112,8 +115,8 @@ USE_I18N = True
USE_L10N = True USE_L10N = True
#USE_TZ = True # USE_TZ = True
#TURNED off for simplicity # TURNED off for simplicity
USE_TZ = False USE_TZ = False

View File

@ -14,16 +14,22 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import path from django.urls import path, include
from django.conf.urls import url from django.conf.urls import url
from .views import calendar, add_event, update, remove from users.views import register
from .views import calendar, add_event, update, remove, all_events
urlpatterns = [ urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
url('^$', calendar, name='calendar'), path('register/', register, name="register"),
url('^$', calendar, name='index'),
url('^add_event$', add_event, name='add_event'), url('^add_event$', add_event, name='add_event'),
url('^update$', update, name='update'), url('^update$', update, name='update'),
url('^remove', remove, name='remove'), url('^remove', remove, name='remove'),
url('^all_events', all_events, name='all_events'),
] ]

View File

@ -1,42 +1,85 @@
from .models import Events """NMGFitness views"""
from django.shortcuts import render from django.shortcuts import render
from django.http import JsonResponse from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from .models import Events
@login_required
def calendar(request): def calendar(request):
all_events = Events.objects.all() """The main calendar view"""
context = { return render(request, 'calendar.html')
"events":all_events,
}
return render(request,'calendar.html',context)
@login_required
def all_events(request):
"""
Json api that returns all events
"""
events = Events.objects.all()
out = []
for event in events:
out.append({
'title': event.user.username,
'id': event.id,
'start': event.start.strftime("%m/%d/%Y, %H:%M:%S"),
'end': event.end.strftime("%m/%d/%Y, %H:%M:%S"),
})
return JsonResponse(out, safe=False)
@login_required
def add_event(request): def add_event(request):
"""
Json api to add an event
given a start and end time in the request get parameters
"""
start = request.GET.get("start", None) start = request.GET.get("start", None)
end = request.GET.get("end", None) end = request.GET.get("end", None)
title = request.GET.get("title", None) event = Events(start=start, end=end, user=request.user)
event = Events(name=str(title), start=start, end=end)
event.save() event.save()
data = {} data = {}
return JsonResponse(data) return JsonResponse(data)
@login_required
def update(request): def update(request):
"""
Json api to update an event
Given an event id, start and end time
Only events from the loged in user are accepted for updates
"""
updateid = request.GET.get("id", None)
event = Events.objects.get(id=updateid)
if request.user != event.user:
return JsonResponse({'Unauthorized': 'wrong user'}, status=401)
start = request.GET.get("start", None) start = request.GET.get("start", None)
end = request.GET.get("end", None) end = request.GET.get("end", None)
title = request.GET.get("title", None)
id = request.GET.get("id", None)
event = Events.objects.get(id=id)
event.start = start event.start = start
event.end = end event.end = end
event.name = title
event.save() event.save()
data = {} data = {}
return JsonResponse(data) return JsonResponse(data)
@login_required
def remove(request): def remove(request):
id = request.GET.get("id", None) """
event = Events.objects.get(id=id) Json api to remove an event
Given an event id
Only events from the loged in user are accepted for removal
"""
updateid = request.GET.get("id", None)
event = Events.objects.get(id=updateid)
if request.user != event.user:
return JsonResponse({'Unauthorized': 'wrong user'}, status=401)
event.delete() event.delete()
data = {} data = {}
return JsonResponse(data) return JsonResponse(data)

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
django-bootstrap4

0
users/__init__.py Normal file
View File

7
users/apps.py Normal file
View File

@ -0,0 +1,7 @@
"""Django app config for the Users app"""
from django.apps import AppConfig
class UsersConfig(AppConfig):
"""Users App AppConfig"""
name = 'users'

8
users/forms.py Normal file
View File

@ -0,0 +1,8 @@
"""Users forms"""
from django.contrib.auth.forms import UserCreationForm
class CustomUserCreationForm(UserCreationForm):
"""Custom user creation form that adds an email field"""
class Meta(UserCreationForm.Meta):
fields = UserCreationForm.Meta.fields + ("email",)

View File

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block content %}
<p>Logged out!</p>
<p>Thanks for spending some quality time with the NMG fittness calendar site today.
</p>
<a href="{% url 'login'%}">Click here to login again.</a>
{% endblock %}

View File

@ -0,0 +1,29 @@
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block content %}
<div class="col-8">
<h2>Login</h2>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit">Login</button>
</p>
</form>
<p>
<a href="{% url 'index' %}">Back to Calendar</a>
</p>
<p>
<a href="{% url 'register' %}">Register</a>
</p>
<p>
<a href="{% url 'password_reset' %}">Reset Password</a>
</p>
</div>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<h2>Password changed</h2>
<a href="{% url 'index' %}">Back to index</a>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block content %}
<div class="col-8">
<h2>Change Password</h2>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" value="Change">
</form>
<a href="{% url 'index' %}">Back to index</a>
</div>
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends 'base.html' %}
{% block content %}
<h2>Password reset complete</h2>
<a href="{% url 'login' %}">Back to login</a>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block content %}
<div class="col-8">
<h2>Confirm password reset</h2>
{% if validlink %}
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" value="Confirm">
</form>
{% else %}
<p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p>
{% endif %}
</div>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<h2>Password reset done</h2>
<a href="{% url 'login' %}">Back to login</a>
{% endblock %}

View File

@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block content %}
<div class="col-8">
<h2>Send password reset link</h2>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
<p>
<input type="submit" value="Reset">
</p>
</form>
<a href="{% url 'index' %}">Back to Calendar</a>
</div>
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block content %}
<div class="col-8">
<h2>Register</h2>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" value="Register">
</form>
<a href="{% url 'login' %}">Back to login</a>
</div>
<p>
Your username will be visible to other users of this platform
</p>
<p>
After registration your account will need to be activated.
Contact someone from the Fintess Commitee with your username to get your account activated.
</p>
{% endblock %}

24
users/views.py Normal file
View File

@ -0,0 +1,24 @@
"""Users views"""
from django.shortcuts import redirect, render
from django.urls import reverse
from users.forms import CustomUserCreationForm
def register(request):
"""Register view, uses the CustomUserCreationForm to register a user"""
if request.method == "POST":
form = CustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save()
user.is_active = False
user.save()
return redirect(reverse("index"))
else:
form = CustomUserCreationForm()
return render(
request, "users/register.html",
{"form": form}
)