瀏覽代碼

Have the basic authentication system working and starting to work on the timesheet page functionality.

Adam Day 4 年之前
父節點
當前提交
14c43d6541
共有 9 個文件被更改,包括 201 次插入36 次删除
  1. 5 2
      TimeSheet/settings.py
  2. 17 1
      app/admin.py
  3. 16 3
      app/forms.py
  4. 39 6
      app/models.py
  5. 19 0
      app/templates/forms/timesheet_entry.html
  6. 2 2
      app/templates/home.html
  7. 20 2
      app/templates/timesheet.html
  8. 32 0
      app/templates/tools/keypad.html
  9. 51 20
      app/views.py

+ 5 - 2
TimeSheet/settings.py

@@ -37,6 +37,8 @@ INSTALLED_APPS = [
     'django.contrib.sessions',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'django.contrib.staticfiles',
+    'crispy_forms',
+    'crispy_bootstrap5',
     'app',
     'app',
 ]
 ]
 
 
@@ -81,12 +83,14 @@ DATABASES = {
     }
     }
 }
 }
 
 
+CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
+CRISPY_TEMPLATE_PACK = "bootstrap5"
+
 
 
 # Password validation
 # Password validation
 # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
 # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
 
 
 AUTH_PASSWORD_VALIDATORS = [
 AUTH_PASSWORD_VALIDATORS = [
-    '''
     {
     {
         'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
         'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
     },
     },
@@ -99,7 +103,6 @@ AUTH_PASSWORD_VALIDATORS = [
     {
     {
         'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
         'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
     },
     },
-    '''
 ]
 ]
 
 
 
 

+ 17 - 1
app/admin.py

@@ -1,9 +1,25 @@
 from django.contrib import admin
 from django.contrib import admin
-from . models import User
+from . models import User, Project, Message, Entry
 # Register your models here.
 # Register your models here.
 
 
 
 
 @admin.register(User)
 @admin.register(User)
 class UserAdmin(admin.ModelAdmin):
 class UserAdmin(admin.ModelAdmin):
     list_display = ['last_name', 'first_name', 'status']
     list_display = ['last_name', 'first_name', 'status']
+    search_fields = ('last_name', 'first_name')
+    ordering = ['last_name', 'first_name']
 
 
+
+@admin.register(Project)
+class ProjectAdmin(admin.ModelAdmin):
+    ordering = ['name']
+
+
+@admin.register(Entry)
+class EntryAdmin(admin.ModelAdmin):
+    pass
+
+
+@admin.register(Message)
+class MessageAdmin(admin.ModelAdmin):
+    pass

+ 16 - 3
app/forms.py

@@ -1,18 +1,31 @@
 from django import forms
 from django import forms
-from django.core.validators import RegexValidator
-from . models import User
+from django.core.validators import RegexValidator, MaxLengthValidator
+from . models import Project
 
 
 numeric = RegexValidator(r'^[0-9+]', 'Only numeric characters.')
 numeric = RegexValidator(r'^[0-9+]', 'Only numeric characters.')
+time_max_length = MaxLengthValidator(4, 'Length limit exceeds 4 characters')
 
 
 
 
 class LoginForm(forms.Form):
 class LoginForm(forms.Form):
     pin = forms.CharField(strip=True, widget=forms.PasswordInput(attrs={
     pin = forms.CharField(strip=True, widget=forms.PasswordInput(attrs={
         'class': 'form-control form-control-lg p-4 text-center',
         'class': 'form-control form-control-lg p-4 text-center',
         'id': 'pin',
         'id': 'pin',
-    }), label=None, validators=[numeric])
+        'placeholder': 'PIN',
+    }), validators=[numeric])
 
 
 
 
 class CreateUserForm(forms.Form):
 class CreateUserForm(forms.Form):
     first_name = forms.CharField(strip=True, required=True)
     first_name = forms.CharField(strip=True, required=True)
     last_name = forms.CharField(strip=True, required=True)
     last_name = forms.CharField(strip=True, required=True)
     pin = forms.CharField(strip=True, required=True, validators=[numeric], help_text="Numeric values only")
     pin = forms.CharField(strip=True, required=True, validators=[numeric], help_text="Numeric values only")
+
+
+class TimeEntryForm(forms.Form):
+    project = forms.ModelChoiceField(Project.objects.all(), required=False, widget=forms.Select(attrs={
+        'class': 'form-control form-control-lg'
+    }))
+    time_worked = forms.CharField(required=True, label="Hours Worked", widget=forms.TextInput(attrs={
+        'class': 'form-control form-control-lg',
+        'max-length': '4',
+        'placeholder': 'Examples: 0.25, 1.5, 2'
+    }), validators=[time_max_length, numeric])

+ 39 - 6
app/models.py

@@ -1,20 +1,53 @@
 from django.db import models
 from django.db import models
 from hashlib import sha256
 from hashlib import sha256
+import datetime
+
+levels = (
+    ('1', 'Standard User'),
+    ('2', 'Supervisor'),
+    ('3', 'Administrator')
+)
 
 
 
 
-# Create your models here.
 class User(models.Model):
 class User(models.Model):
     status = models.BooleanField(default=True, blank=True)
     status = models.BooleanField(default=True, blank=True)
     first_name = models.CharField(max_length=255, default="", blank=False)
     first_name = models.CharField(max_length=255, default="", blank=False)
     last_name = models.CharField(max_length=255, default="", blank=False)
     last_name = models.CharField(max_length=255, default="", blank=False)
     pin = models.CharField(max_length=255, null=False, blank=False, unique=True)
     pin = models.CharField(max_length=255, null=False, blank=False, unique=True)
+    level = models.CharField(max_length=255, blank=False, default="1", choices=levels)
 
 
     def save(self, *args, **kwargs):
     def save(self, *args, **kwargs):
-        self.pin = sha256(self.pin.encode('utf-8')).hexdigest()
-
-        user = User.objects.filter(pin=self.pin).first()
-        if user:
-            super(User, self).save(*args, **kwargs)
+        # Compare old vs new
+        if self.pk:
+            obj = User.objects.values('pin').get(pk=self.pk)
+            if obj['pin'] != self.pin:
+                self.pin = sha256(self.pin.encode('utf-8')).hexdigest()
+        super(User, self).save(*args, **kwargs)
 
 
     def __str__(self):
     def __str__(self):
         return "%s, %s (Enabled: %s)" % (self.last_name, self.first_name, self.status)
         return "%s, %s (Enabled: %s)" % (self.last_name, self.first_name, self.status)
+
+
+class Project(models.Model):
+    name = models.CharField(blank=False, max_length=50, default="")
+    description = models.TextField()
+
+    def __str__(self):
+        return "%s" % (self.name,)
+
+
+class Entry(models.Model):
+    user_id = models.IntegerField(blank=False, editable=False)
+    date = models.DateField(blank=False)
+    project = models.ForeignKey(Project, on_delete=models.CASCADE, blank=True)
+    time_worked = models.CharField(max_length=4, default=0, blank=False)
+    note = models.TextField(blank=True)
+
+
+class Message(models.Model):
+    message = models.TextField(blank=False)
+    expire_date = models.DateTimeField(blank=False)
+    recipient = models.ForeignKey(User, on_delete=models.CASCADE, blank=False)
+
+    def __str__(self):
+        return "%s" % (self.message,)

+ 19 - 0
app/templates/forms/timesheet_entry.html

@@ -0,0 +1,19 @@
+{% load crispy_forms_tags %}
+
+<form action="" method="post">
+    <div class="row">
+        <div class="col-sm-12 col-md-6">
+            {{ form.project | as_crispy_field }}
+        </div>
+        <div class="col-sm-12 col-md-6">
+            {{ form.time_worked | as_crispy_field }}
+        </div>
+        <div class="col-12">
+            {% include 'tools/keypad.html' %}
+        </div>
+        <div class="col-12 text-center">
+            <input type="submit" value="Submit" class="btn btn-lg btn-dark">
+        </div>
+        {% csrf_token %}
+    </div>
+</form>

+ 2 - 2
app/templates/home.html

@@ -5,7 +5,7 @@
         {% csrf_token %}
         {% csrf_token %}
         {% if form.pin.errors %}
         {% if form.pin.errors %}
         <div class="row mb-3 justify-content-center" id="error_wrapper">
         <div class="row mb-3 justify-content-center" id="error_wrapper">
-            <div class="col-sm-12 col-md-6 text-danger">
+            <div class="col-sm-12 text-danger text-center">
                 <ul class="list-unstyled">
                 <ul class="list-unstyled">
                 {% for error in form.pin.errors %}
                 {% for error in form.pin.errors %}
                     <li><span class="oi" data-glyph="warning"></span> {{ error }}</li>
                     <li><span class="oi" data-glyph="warning"></span> {{ error }}</li>
@@ -15,7 +15,7 @@
         </div>
         </div>
         {% endif %}
         {% endif %}
         <div class="row mb-3 justify-content-center">
         <div class="row mb-3 justify-content-center">
-            <div class="col-sm-12 col-md-6">
+            <div class="col-sm-12">
                 {{ form.pin }}
                 {{ form.pin }}
             </div>
             </div>
         </div>
         </div>

+ 20 - 2
app/templates/timesheet.html

@@ -1,5 +1,23 @@
 {% extends 'layout.html' %}
 {% extends 'layout.html' %}
-
+{% load crispy_forms_tags %}
 {% block content %}
 {% block content %}
-{{ title }}
+
+    <div class="row mb-3">
+        <div class="col-sm-12 col-md-6">
+            <h2>{{ user.first_name | title }} {{ user.last_name | title }}</h2>
+        </div>
+        <div class="col-sm-12 col-md-6 text-end">
+            {% if user.level == '3' %}
+            <a href="#!" class="btn btn-lg btn-secondary"><span class="oi" data-glyph="wrench"></span></a>
+            {% endif %}
+            <a href="#!" class="btn btn-lg btn-secondary">Reports</a>
+            <a href="{% url 'logout' %}" class="btn btn-lg btn-danger">Logout</a>
+        </div>
+    </div>
+    <div class="row">
+        <div class="col-12">
+            {% include 'forms/timesheet_entry.html' %}
+        </div>
+    </div>
+
 {% endblock %}
 {% endblock %}

+ 32 - 0
app/templates/tools/keypad.html

@@ -0,0 +1,32 @@
+{% load static %}
+
+<div class="keyboard d-none d-md-none d-lg-block mb-3">
+    <div class="card shadow-sm">
+        <div class="card-body">
+            <div class="row mb-3 justify-content-center g-0">
+                <div class="col-3 d-grid gap-2 mx-auto"><a href="#!" data-value="1" data-alt-value="!" class="btn btn-lg btn-light border keyboard-key">1</a></div>
+                <div class="col-3 d-grid gap-2 mx-auto"><a href="#!" data-value="2" data-alt-value="@" class="btn btn-lg btn-light border keyboard-key">2</a></div>
+                <div class="col-3 d-grid gap-2 mx-auto"><a href="#!" data-value="3" data-alt-value="#" class="btn btn-lg btn-light border keyboard-key">3</a></div>
+            </div>
+
+            <div class="row mb-3 justify-content-center g-0">
+                <div class="col-3 d-grid gap-2 mx-auto"><a href="#!" data-value="4" data-alt-value="$" class="btn btn-lg btn-light border keyboard-key">4</a></div>
+                <div class="col-3 d-grid gap-2 mx-auto"><a href="#!" data-value="5" data-alt-value="%" class="btn btn-lg btn-light border keyboard-key">5</a></div>
+                <div class="col-3 d-grid gap-2 mx-auto"><a href="#!" data-value="6" data-alt-value="^" class="btn btn-lg btn-light border keyboard-key">6</a></div>
+            </div>
+
+            <div class="row mb-3 justify-content-center g-0">
+                <div class="col-3 d-grid gap-2 mx-auto"><a href="#!" data-value="7" data-alt-value="&" class="btn btn-lg btn-light border keyboard-key">7</a></div>
+                <div class="col-3 d-grid gap-2 mx-auto"><a href="#!" data-value="8" data-alt-value="*" class="btn btn-lg btn-light border keyboard-key">8</a></div>
+                <div class="col-3 d-grid gap-2 mx-auto"><a href="#!" data-value="9" data-alt-value="(" class="btn btn-lg btn-light border keyboard-key">9</a></div>
+            </div>
+
+            <div class="row mb-3 g-0">
+                <div class="col-3 d-grid gap-2 mx-auto"><a href="#!" data-value="0" data-alt-value=")" class="btn btn-lg btn-light border keyboard-key">0</a></div>
+                <div class="col-3 d-grid gap-2 mx-auto"><a href="#!" data-value="." data-alt-value=")" class="btn btn-lg btn-light border keyboard-key">.</a></div>
+                <div class="col-3 d-grid gap-2 mx-auto"><a href="#!" id="back-key" data-value="back" class="btn btn-lg btn-light border">Delete</a></div>
+            </div>
+        </div>
+    </div>
+</div>
+<script src="{% static 'app/js/keyboard.js' %}"></script>

+ 51 - 20
app/views.py

@@ -1,25 +1,43 @@
 from django.shortcuts import render, redirect
 from django.shortcuts import render, redirect
-from . forms import LoginForm
+from . forms import LoginForm, CreateUserForm, TimeEntryForm
 from . models import User
 from . models import User
 from hashlib import sha256
 from hashlib import sha256
-from django.core import serializers
-from django.contrib.auth import authenticate, login, logout
-from django.contrib.auth.decorators import login_required
-from .forms import CreateUserForm
+
+
+def hash_pin(pin):
+    return sha256(pin.encode('utf-8')).hexdigest()
 
 
 
 
 def logout_user(request):
 def logout_user(request):
-    logout(request)
+    del request.session['authenticated']
     return redirect('home')
     return redirect('home')
 
 
 
 
+def requires_auth(request):
+    return request.session.get('authenticated')
+
+
+def get_user(uid):
+    user = User.objects.get(id=uid)
+    return user
+
+
 def create_user(request):
 def create_user(request):
-    form = CreateUserForm
+    form = CreateUserForm()
     if request.method == "POST":
     if request.method == "POST":
         form = CreateUserForm(request.POST)
         form = CreateUserForm(request.POST)
         if form.is_valid():
         if form.is_valid():
             data = form.cleaned_data
             data = form.cleaned_data
-            print(data)
+            users = User.objects.filter(pin=hash_pin(data['pin']))
+            if len(users) == 0:
+                user = User()
+                user.first_name = data['first_name']
+                user.last_name = data['last_name']
+                user.pin = data['pin']
+                user.save()
+                return redirect('timesheet')
+            else:
+                form.add_error('pin', 'PIN already exists')
 
 
     context = {
     context = {
         'form': form
         'form': form
@@ -30,21 +48,20 @@ def create_user(request):
 
 
 def home(request):
 def home(request):
     form = LoginForm
     form = LoginForm
-
     login_error = False
     login_error = False
     if request.method == "POST":
     if request.method == "POST":
         form = LoginForm(request.POST or None)
         form = LoginForm(request.POST or None)
         if form.is_valid():
         if form.is_valid():
             data = form.cleaned_data
             data = form.cleaned_data
             pin = sha256(data['pin'].encode('utf-8')).hexdigest()
             pin = sha256(data['pin'].encode('utf-8')).hexdigest()
-            print(pin)
-            user = User.objects.filter(pin=pin)
-
+            user = User.objects.filter(pin=pin).first()
+            print(user)
             if user is None:
             if user is None:
                 form.add_error('pin', 'Invalid login')
                 form.add_error('pin', 'Invalid login')
                 login_error = True
                 login_error = True
             else:
             else:
-                request.session['u'] = serializers.serialize('json', user)
+                request.session['authenticated'] = True
+                request.session['uid'] = user.id
                 return redirect('timesheet')
                 return redirect('timesheet')
 
 
     context = {
     context = {
@@ -55,11 +72,25 @@ def home(request):
     return render(request, 'home.html', context=context)
     return render(request, 'home.html', context=context)
 
 
 
 
-@login_required(login_url='/')
 def timesheet(request):
 def timesheet(request):
-
-    context = {
-
-    }
-
-    return render(request, 'timesheet.html', context=context)
+    if requires_auth(request):
+        uid = request.session.get('uid')
+        user = get_user(uid)
+
+        form = TimeEntryForm()
+
+        if request.method == "POST":
+            form = TimeEntryForm(request.POST)
+            if form.is_valid():
+                data = form.cleaned_data
+                print(data)
+
+        context = {
+            'user': user,
+            'form': form,
+        }
+
+        return render(request, 'timesheet.html', context=context)
+    else:
+        request.session['authenticated'] = False
+        return redirect('home')