diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..9c3cd1e --- /dev/null +++ b/.drone.yml @@ -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 diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..1cba4bf --- /dev/null +++ b/.pylintrc @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..3bd0dc6 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +[![Build Status](https://drone.caret.be/api/badges/jens/nmgfitness/status.svg)](https://drone.caret.be/jens/nmgfitness) diff --git a/manage.py b/manage.py index b4c37f2..5bdda86 100755 --- a/manage.py +++ b/manage.py @@ -3,18 +3,12 @@ import os import sys +from django.core.management import execute_from_command_line + def main(): """Run administrative tasks.""" 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) diff --git a/nmgfitness/admin.py b/nmgfitness/admin.py index 31807c0..e11146e 100644 --- a/nmgfitness/admin.py +++ b/nmgfitness/admin.py @@ -1,3 +1,4 @@ +"""Admin module""" from django.apps import apps from django.contrib import admin diff --git a/nmgfitness/base.html b/nmgfitness/base.html new file mode 100644 index 0000000..fc5470b --- /dev/null +++ b/nmgfitness/base.html @@ -0,0 +1,79 @@ + + + + + + + + + NMG Fitness Registration + + + + + + +
+
+ {% block content %} + BASE TEMPLATE + {% endblock %} +
+
+ + + + + + + + + + diff --git a/nmgfitness/calendar.html b/nmgfitness/calendar.html index bfed48b..4206389 100644 --- a/nmgfitness/calendar.html +++ b/nmgfitness/calendar.html @@ -1,13 +1,7 @@ - - - - - - - - - - -

Nieuwe Molens Gent -- Fitness calendar


-

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

Je kan een event verschuiven of verlengen/verkorten door het vast te nemen en te verschuiven.

-

- Indien het niet lukt om op een reeds gerserveerd moment een nieuwe reservatie aan te maken, -

- - +{% endblock %} diff --git a/nmgfitness/migrations/0002_auto_20210805_2157.py b/nmgfitness/migrations/0002_auto_20210805_2157.py new file mode 100644 index 0000000..cce821d --- /dev/null +++ b/nmgfitness/migrations/0002_auto_20210805_2157.py @@ -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, + ), + ] diff --git a/nmgfitness/models.py b/nmgfitness/models.py index 4dab97e..2cc4580 100644 --- a/nmgfitness/models.py +++ b/nmgfitness/models.py @@ -1,11 +1,20 @@ +"""Nmgfitness Events model""" from django.db import models +from django.conf import settings + 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) - name = models.CharField(max_length=255,null=True,blank=True) - start = models.DateTimeField(null=True,blank=True) - end = models.DateTimeField(null=True,blank=True) + start = 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): - return self.name - + return self.user.username diff --git a/nmgfitness/settings.py b/nmgfitness/settings.py index 2ab1d9f..690e7ba 100644 --- a/nmgfitness/settings.py +++ b/nmgfitness/settings.py @@ -27,17 +27,20 @@ DEBUG = True ALLOWED_HOSTS = [] - +LOGIN_URL = '/accounts/login/' +LOGIN_REDIRECT_URL = '/' # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', + 'users.apps.UsersConfig', + 'nmgfitness', 'django.contrib.auth', + 'django.contrib.admin', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'nmgfitness', + 'bootstrap4', ] MIDDLEWARE = [ @@ -112,8 +115,8 @@ USE_I18N = True USE_L10N = True -#USE_TZ = True -#TURNED off for simplicity +# USE_TZ = True +# TURNED off for simplicity USE_TZ = False diff --git a/nmgfitness/urls.py b/nmgfitness/urls.py index 99f8465..b4485cc 100644 --- a/nmgfitness/urls.py +++ b/nmgfitness/urls.py @@ -14,16 +14,22 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, include 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 = [ + path('accounts/', include('django.contrib.auth.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('^update$', update, name='update'), url('^remove', remove, name='remove'), + url('^all_events', all_events, name='all_events'), ] diff --git a/nmgfitness/views.py b/nmgfitness/views.py index 1ee740d..6a96bb2 100644 --- a/nmgfitness/views.py +++ b/nmgfitness/views.py @@ -1,42 +1,85 @@ -from .models import Events - +"""NMGFitness views""" from django.shortcuts import render from django.http import JsonResponse +from django.contrib.auth.decorators import login_required +from .models import Events + + +@login_required def calendar(request): - all_events = Events.objects.all() - context = { - "events":all_events, - } - return render(request,'calendar.html',context) + """The main calendar view""" + return render(request, 'calendar.html') + +@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): + """ + Json api to add an event + given a start and end time in the request get parameters + """ + start = request.GET.get("start", None) end = request.GET.get("end", None) - title = request.GET.get("title", None) - event = Events(name=str(title), start=start, end=end) + event = Events(start=start, end=end, user=request.user) event.save() data = {} return JsonResponse(data) +@login_required 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) 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.end = end - event.name = title event.save() data = {} return JsonResponse(data) +@login_required 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() data = {} return JsonResponse(data) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5284932 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +django-bootstrap4 diff --git a/users/__init__.py b/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/users/apps.py b/users/apps.py new file mode 100644 index 0000000..340d2a5 --- /dev/null +++ b/users/apps.py @@ -0,0 +1,7 @@ +"""Django app config for the Users app""" +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + """Users App AppConfig""" + name = 'users' diff --git a/users/forms.py b/users/forms.py new file mode 100644 index 0000000..c236c41 --- /dev/null +++ b/users/forms.py @@ -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",) diff --git a/users/templates/registration/logged_out.html b/users/templates/registration/logged_out.html new file mode 100644 index 0000000..fa69c3f --- /dev/null +++ b/users/templates/registration/logged_out.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block content %} +

Logged out!

+

Thanks for spending some quality time with the NMG fittness calendar site today. +

+ Click here to login again. +{% endblock %} diff --git a/users/templates/registration/login.html b/users/templates/registration/login.html new file mode 100644 index 0000000..b790574 --- /dev/null +++ b/users/templates/registration/login.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} + +{% load bootstrap4 %} +{% block content %} +
+

Login

+ + +
+ {% csrf_token %} + + {% bootstrap_form form %} + +

+
+ + +

+Back to Calendar +

+

+Register +

+

+Reset Password +

+ +
+{% endblock %} diff --git a/users/templates/registration/password_change_done.html b/users/templates/registration/password_change_done.html new file mode 100644 index 0000000..1d6f75b --- /dev/null +++ b/users/templates/registration/password_change_done.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} + +{% block content %} +

Password changed

+ +Back to index +{% endblock %} diff --git a/users/templates/registration/password_change_form.html b/users/templates/registration/password_change_form.html new file mode 100644 index 0000000..5b340e9 --- /dev/null +++ b/users/templates/registration/password_change_form.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} + +{% load bootstrap4 %} +{% block content %} +
+

Change Password

+ +
+ {% csrf_token %} + {% bootstrap_form form %} + +
+ +Back to index +
+{% endblock %} diff --git a/users/templates/registration/password_reset_complete.html b/users/templates/registration/password_reset_complete.html new file mode 100644 index 0000000..27710da --- /dev/null +++ b/users/templates/registration/password_reset_complete.html @@ -0,0 +1,8 @@ + +{% extends 'base.html' %} + +{% block content %} +

Password reset complete

+ +Back to login +{% endblock %} diff --git a/users/templates/registration/password_reset_confirm.html b/users/templates/registration/password_reset_confirm.html new file mode 100644 index 0000000..f647065 --- /dev/null +++ b/users/templates/registration/password_reset_confirm.html @@ -0,0 +1,23 @@ +{% extends 'base.html' %} + +{% load bootstrap4 %} +{% block content %} +
+

Confirm password reset

+ + +{% if validlink %} + +
+ {% csrf_token %} + {% bootstrap_form form %} + +
+ +{% else %} + +

The password reset link was invalid, possibly because it has already been used. Please request a new password reset.

+ +{% endif %} +
+{% endblock %} diff --git a/users/templates/registration/password_reset_done.html b/users/templates/registration/password_reset_done.html new file mode 100644 index 0000000..a18db15 --- /dev/null +++ b/users/templates/registration/password_reset_done.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} + +{% block content %} +

Password reset done

+ +Back to login +{% endblock %} diff --git a/users/templates/registration/password_reset_form.html b/users/templates/registration/password_reset_form.html new file mode 100644 index 0000000..ed14067 --- /dev/null +++ b/users/templates/registration/password_reset_form.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} + +{% load bootstrap4 %} +{% block content %} +
+

Send password reset link

+ +
+ {% csrf_token %} + + {% bootstrap_form form %} +

+ +

+
+ +Back to Calendar +
+{% endblock %} diff --git a/users/templates/users/register.html b/users/templates/users/register.html new file mode 100644 index 0000000..b4f5286 --- /dev/null +++ b/users/templates/users/register.html @@ -0,0 +1,25 @@ +{% extends 'base.html' %} + +{% load bootstrap4 %} + +{% block content %} +
+

Register

+ +
+ {% csrf_token %} + {% bootstrap_form form %} + +
+ +Back to login +
+ +

+Your username will be visible to other users of this platform +

+

+After registration your account will need to be activated. +Contact someone from the Fintess Commitee with your username to get your account activated. +

+{% endblock %} diff --git a/users/views.py b/users/views.py new file mode 100644 index 0000000..b164c5e --- /dev/null +++ b/users/views.py @@ -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} + )