diff --git a/README.md b/README.md index 25b37302..4347f0e1 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

----h +--- ## Overview @@ -36,8 +36,4 @@ To contribute changes to the original respository, the following commands can be - git merge upstream/master -- git push origin master - -## Colour Schemes - -- Options for modifying colour schemes can be found in the profile settings page, under the "App Information" category. Different colours can be picked for altering Bank Holidays and Weekends. A dropdown list within the same container can be modified to choose whether these specific dates are shown on the calendar. The rectangular bar can be selected to show a colour picker where a specific colour can be chosen, after these changes have been made they can be confirmed by clicking on the "Submit" button to apply them. \ No newline at end of file +- git push origin master \ No newline at end of file diff --git a/ap_src/ap_app/absences.py b/ap_src/ap_app/absences.py deleted file mode 100644 index f48b782d..00000000 --- a/ap_src/ap_app/absences.py +++ /dev/null @@ -1,307 +0,0 @@ -#get_absence_data -#text_rules -#profile_page -#absence_delete -#absence_recurring_delete -#edit_recurring_absences -#add_recurring - -import calendar -import datetime -import json -import holidays -import pycountry -import pandas as pd -import requests -from datetime import timedelta - -from django.contrib import messages -from django.contrib.auth import get_user_model -from django.contrib.auth.decorators import login_required -from django.contrib.auth.forms import UserCreationForm -from django.db.models.functions import Lower -from django.http import HttpResponse, JsonResponse -from django.shortcuts import redirect, render -from django.urls import reverse, reverse_lazy -from django.views.generic import CreateView, UpdateView - -from .teams import * -from .objects import * -from .teams import * - -from .forms import * -from .models import (Absence, RecurringAbsences, Relationship, Role, Team, - UserProfile, RecurringException) - -def get_absence_data(users, user_type): - data = {} - absence_content = [] - total_absence_dates = {} - total_half_dates = {} - total_recurring_dates = {} - all_absences = {} - delta = datetime.timedelta(days=1) - - for user in users: - # all the absences for the user - if user_type == 1: - user_username = user.user.username - else: - user_username = user.username - - absence_info = Absence.objects.filter(Target_User_ID__username=user_username) - total_absence_dates[user_username] = [] - total_recurring_dates[user_username] = [] - total_half_dates[user_username] = [] - all_absences[user_username] = [] - - # if they have any absences - if absence_info: - # mapping the absence content to keys in dictionary - for x in absence_info: - absence_id = x.ID - absence_date_start = x.absence_date_start - absence_date_end = x.absence_date_end - dates = absence_date_start - if x.half_day == "NORMAL": - while dates <= absence_date_end: - total_absence_dates[user_username].append(dates) - dates += delta - else: - total_half_dates[user_username].append( - { - "date": absence_date_start, - "type": x.half_day - } - ) - - absence_content.append( - { - "ID": absence_id, - "absence_date_start": absence_date_start, - "absence_date_end": absence_date_end, - "dates": total_absence_dates[user_username], - } - ) - - # for each user it maps the set of dates to a dictionary key labelled as the users name - all_absences[user_username] = absence_content - - recurring = RecurringAbsences.objects.filter(Target_User_ID__username=user_username) - - if recurring: - for recurrence_ in recurring: - dates = recurrence_.Recurrences.occurrences( - dtend=datetime.datetime.strptime( - str(datetime.datetime.now().year + 2), "%Y" - ) - ) - - for x in list(dates)[:-1]: - time_const = "23:00:00" - time_var = datetime.datetime.strftime(x, "%H:%M:%S") - if time_const == time_var: - x += timedelta(days=1) - - #print(RecurringException.objects.filter(Target_User_ID__username=user_username, Exception_Start=x).count()) - if RecurringException.objects.filter(Target_User_ID__username=user_username, Exception_Start=x).count() == 0: - total_recurring_dates[user_username].append(x) - # TODO: add auto deleting for recurring absences once last date of absences in before now - # if x < datetime.datetime.now(): - # pass - - data["recurring_absence_dates"] = total_recurring_dates - data["all_absences"] = all_absences - data["absence_dates"] = total_absence_dates - data["half_days_data"] = total_half_dates - data["users"] = users - return data - -def text_rules(user): - recurring_absences = RecurringAbsences.objects.filter(Target_User_ID=user).values( - "Recurrences", "ID" - ) - rec_absences = {} - - for x in recurring_absences: - absence_ = x["Recurrences"] - if absence_: - rec_absences[x["ID"]] = [] - if absence_.exdates: - for y in absence_.exdates: - rec_absences[x["ID"]].append( - "Excluding Date: " + (y + timedelta(days=1)).strftime("%A,%d %B,%Y") - ) - if absence_.rdates: - for y in absence_.rdates: - rec_absences[x["ID"]].append( - "Date: " + (y + timedelta(days=1)).strftime("%A,%d %B,%Y") - ) - if absence_.rrules: - for y in absence_.rrules: - rec_absences[x["ID"]].append("Rule: " + str(y.to_text())) - - if absence_.exrules: - for y in absence_.exrules: - rec_absences[x["ID"]].append("Excluding Rule: " + str(y.to_text())) - - return rec_absences - - -# Profile page -@login_required -def profile_page(request): - if request.method == "POST": - form = SwitchUser( - request.POST, - initial={"user": request.user}, - ) - form.fields["user"].queryset = UserProfile.objects.filter( - edit_whitelist=request.user - ) - users = UserProfile.objects.filter(edit_whitelist=request.user) - rec_absences = text_rules(request.user) - - if form.is_valid(): - absence_user = form.cleaned_data["user"].user - - absences = Absence.objects.filter(Target_User_ID=absence_user) - rec_absences = text_rules(absence_user) - return render( - request, - "ap_app/profile.html", - { - "form": form, - "message": "Successfully switched user", - "absences": absences, - "users": users, - "recurring_absences": rec_absences, - }, - ) - else: - absences = Absence.objects.filter(Target_User_ID=request.user.id) - rec_absences = text_rules(request.user) - - users = UserProfile.objects.filter(edit_whitelist=request.user) - form = SwitchUser() - form.fields["user"].queryset = users - form.fields["user"].initial = request.user - - return render( - request, - "ap_app/profile.html", - { - "absences": absences, - "users": users, - "form": form, - "recurring_absences": rec_absences, - }, - ) - -@login_required -def absence_delete(request, absence_id: int, user_id: int, team_id: int = 1): - try: - absence = Absence.objects.get(pk=absence_id) - absence.delete() - except Absence.DoesNotExist: - pass - if request.user == User.objects.get(id = user_id): - return redirect("profile") - return redirect("edit_team_member_absence", team_id, user_id) - - -@login_required -def absence_recurring_delete( - request, absence_id: int, user_id: int, team_id: int = None -): - absence = RecurringAbsences.objects.get(pk=absence_id) - user = request.user - absence.delete() - if user == absence.Target_User_ID: - return redirect("profile") - return redirect("edit_team_member_absence", team_id, user_id) - - -class EditAbsence(UpdateView): - template_name = "ap_app/edit_absence.html" - model = Absence - - # specify the fields - fields = ["absence_date_start", "absence_date_end"] - - def get_success_url(self) -> str: - return reverse("profile") - - -@login_required -def edit_recurring_absences(request, pk): - absence = RecurringAbsences.objects.get(ID=pk) - - if request.method == "POST": - form = RecurringAbsencesForm(request.POST, instance=absence) - form2 = TargetUserForm(request.POST, target_user=request.user) - form2.fields["target_user"].queryset = UserProfile.objects.filter( - edit_whitelist__in=[request.user] - ) - rule = str(form["Recurrences"].value()) - - if not ("DAILY" in rule or "BY" in rule): - content = { - "form": form, - "form2": form2, - "message": "Must select a day/month", - } - return render(request, "ap_app/edit_recurring_absence.html", content) - - if form2.is_valid(): - absence.Target_User_ID = form2.cleaned_data["target_user"].user - absence.recurrences = form["Recurrences"].value() - absence.save() - return redirect("index") - else: - form = RecurringAbsencesForm(instance=absence) - - form2 = TargetUserForm(target_user=absence.Target_User_ID) - form2.fields["target_user"].queryset = UserProfile.objects.filter( - edit_whitelist__in=[absence.User_ID] - ) - - return render( - request, "ap_app/edit_recurring_absence.html", {"form": form, "form2": form2} - ) - -@login_required -def add_recurring(request) -> render: - if request.method == "POST": - form = RecurringAbsencesForm(request.POST) - form2 = TargetUserForm(request.POST, target_user=request.user) - form2.fields["target_user"].queryset = UserProfile.objects.filter( - edit_whitelist__in=[request.user] - ) - rule = str(form["Recurrences"].value()) - - - if not ("DAILY" in rule or "BY" in rule): - content = { - "form": form, - "form2": form2, - "message": "Must select a day/month", - } - return render(request, "ap_app/add_recurring_absence.html", content) - - if form2.is_valid(): - RecurringAbsences.objects.create( - Recurrences=form["Recurrences"].value(), - Target_User_ID=form2.cleaned_data["target_user"].user, - User_ID=request.user, - ) - return redirect("index") - else: - form = RecurringAbsencesForm() - form2 = TargetUserForm(target_user=request.user) - form2.fields["target_user"].queryset = UserProfile.objects.filter( - edit_whitelist__in=[request.user] - ) - - content = {"form": form, "form2": form2} - return render(request, "ap_app/add_recurring_absence.html", content) \ No newline at end of file diff --git a/ap_src/ap_app/calendarview.py b/ap_src/ap_app/calendarview.py deleted file mode 100644 index ef6c25ee..00000000 --- a/ap_src/ap_app/calendarview.py +++ /dev/null @@ -1,349 +0,0 @@ -#check_calendar_date -#set_calendar_month -#all_calendar -#team_calendar -#api_calendar_view -#get_date_data -#get_filter_users - -import datetime -import requests -import hashlib -import environ -from dateutil.relativedelta import relativedelta - -from django.contrib.auth.decorators import login_required -from django.shortcuts import redirect, render - -from .forms import * -from .models import ( Relationship, Role, Team, - UserProfile, Status, ColorData, ColourScheme) - -from .absences import * -from .teams import * - -def check_calendar_date(year, month) -> bool: - date = datetime.datetime(year, datetime.datetime.strptime(month, "%B").month, 1) - current = datetime.datetime.now() - - if date < (current - relativedelta(years=1)) or date > (current + relativedelta(years=1)): - return False - - return True - -@login_required -def set_calendar_month(request): - if request.method == "POST": - month = request.POST.get('month_names') - - split = month.split() - - month = split[0] - - year = split[1] - - print(month) - - return redirect('all_calendar', month=month, year=year) - -def color_variant(hex_color, brightness_offset=1): - if len(hex_color) != 7: - raise Exception("Passed %s into color_variant(), needs to be in #87c95f format." % hex_color) - rgb_hex = [hex_color[x:x+2] for x in [1, 3, 5]] - new_rgb_int = [int(hex_value, 16) + brightness_offset for hex_value in rgb_hex] - new_rgb_int = [min([255, max([0, i])]) for i in new_rgb_int] # make sure new values are between 0 and 255 - return "rgb" + str(new_rgb_int).replace("[", "(").replace("]", ")") - -def get_colour_data(request:HttpRequest): - colour_data = {} - for scheme in ColourScheme.objects.all(): - scheme_data = {} - data = ColorData.objects.filter(user=request.user, scheme=scheme) - if data.exists(): - scheme_data["enabled"] = data[0].enabled - scheme_data["colour"] = data[0].color - scheme_data["offset"] = color_variant(data[0].color, -30) - else: - scheme_data["enabled"] = True - scheme_data["colour"] = scheme.default - scheme_data["offset"] = color_variant(scheme.default, -30) - - colour_data[scheme.name.replace(" ", "_").lower()] = scheme_data - - return colour_data - -def team_calendar_data(id): - data = None - - try: - response = requests.get(env("TEAM_DATA_URL") + "api/members/?id={}".format(id)) - data = response.json() - except: - # TODO Create error page for API failure - raise NotImplementedError("Failed to retrieve API data (No error page)") - - if response.status_code != 200: - raise NotImplementedError("Failed to retrieve API data (No error page)") - - return data - -#JC - Specific team calendar using the API -@login_required -def api_team_calendar( - request:HttpRequest, - id, - month=datetime.datetime.now().strftime("%B"), - year=datetime.datetime.now().year -): - - date = check_calendar_date(year, month) - if not date: - return redirect("api_team_calendar") - - try: - userprofile: UserProfile = UserProfile.objects.get(user=request.user) - except IndexError: - # TODO Create an error page if a userprofile is not found. - raise NotImplementedError("Invalid User profile (No error page)") - - dates = get_date_data(userprofile.region, month, year) - - team_data = team_calendar_data(id)[0] - - #Get absence data - team_users = [] - user_data = None - for user in team_data["members"]: - user_instance = User.objects.filter(username=user["user"]["username"]) - if user_instance.exists() and user_instance not in team_users: - team_users.append(user_instance[0]) - - if user["user"]["username"] == request.user.username: - user_data = user["user"] - - absence_data = get_absence_data(team_users, 2) - - data = { - **dates, - **absence_data, - "team": team_data, - "user_data": user_data, - "id": id, - "url": env("TEAM_DATA_URL") - } - - return render(request, "api_pages/team_calendar.html", data) - -def get_date_data( - region, - month=datetime.datetime.now().strftime("%B"), - year=datetime.datetime.now().year, -): - # uses a dictionary to get all the data needed for the context - # and concatenates it to form the full context with other dictionaries - data = {} - data["current_year"] = datetime.datetime.now().year - data["current_month"] = datetime.datetime.now().strftime("%B") - data["current_month_num"] = datetime.datetime.now().month - data["today"] = datetime.datetime.now().day - data["year"] = year - data["month"] = month - data["next_current_year"] = datetime.datetime.now().year + 1 - data["next_second_year"] = datetime.datetime.now().year + 2 - - data["previous_current_year"] = datetime.datetime.now().year - 1 - - last_year = datetime.datetime.today().year - 1 - next_year = datetime.datetime.today().year + 1 - - try: - last_year_date = datetime.datetime.strptime(f"{last_year}-{datetime.datetime.today().month}-{datetime.datetime.today().day}",'%Y-%m-%d').date() - next_year_date = datetime.datetime.strptime(f"{next_year}-{datetime.datetime.today().month}-{datetime.datetime.today().day}",'%Y-%m-%d').date() - except ValueError: - last_year_date = datetime.datetime.strptime(f"{last_year}-{datetime.datetime.today().month}-{datetime.datetime.today().day-1}",'%Y-%m-%d').date() - next_year_date = datetime.datetime.strptime(f"{next_year}-{datetime.datetime.today().month}-{datetime.datetime.today().day-1}",'%Y-%m-%d').date() - - - #YYYY/MM/DD - start_date, end_date = last_year_date, next_year_date - - data["month_list"] = pd.period_range(start=start_date, end=end_date, freq='M') - data["month_list"] = [month.strftime("%B %Y") for month in data["month_list"]] - - data["selected_date"] = datetime.date(int(data["year"]), datetime.datetime.strptime(month, "%B").month, 1).strftime("%B %Y") - - data["day_range"] = range( - 1, - calendar.monthrange( - data["year"], datetime.datetime.strptime(month, "%B").month - )[1] - + 1, - ) - data["day_range_num"] = len(list(data["day_range"])) + 1 - data["month_num"] = datetime.datetime.strptime(month, "%B").month - - data["previous_month"] = "December" - data["next_month"] = "January" - data["previous_year"] = year - 1 - data["next_year"] = year + 1 - - # as the month number resets every year try and except statements - # have to be used as at the end and start of a year - # the month cannot be calculated by adding or subtracting 1 - # as 13 and 0 are not datetime month numbers - try: - data["next_month"] = datetime.datetime.strptime( - str((datetime.datetime.strptime(data["month"], "%B")).month + 1), "%m" - ).strftime("%B") - - except ValueError: - pass - try: - data["previous_month"] = datetime.datetime.strptime( - str((datetime.datetime.strptime(data["month"], "%B")).month - 1), "%m" - ).strftime("%B") - except ValueError: - pass - - # calculating which days are weekends to mark them easier in the html - data["days_name"] = [] - for day in data["day_range"]: - date = f"{day} {month} {year}" - date = datetime.datetime.strptime(date, "%d %B %Y") - date = date.strftime("%A")[0:2] - data["days_name"].append(date) - - data["weekend_list"] = [] - for day in data["day_range"]: - date = f"{day} {month} {year}" - date = datetime.datetime.strptime(date, "%d %B %Y") - date = date.strftime("%A")[0:2] - if (date == "Sa" or date == "Su"): - data["weekend_list"].append(day) - - data["bank_hol"] = [] - for h in holidays.country_holidays(region, years=year).items(): - if (h[0].month == data["month_num"]): - data["bank_hol"].append(h[0].day) - - return data - -def get_filter_users(request, users) -> list: - """Used for calendar filtering system - (Returns list of filtered users depending on - search-bar and 'filter by absence' checkbox""" - - filtered_users = [] - - # Filtering by both username & absence - if "username" in request.GET and "absent" in request.GET: - # Get username input & limits the length to 50 - name_filtered_by = request.GET["username"][:50] - for absence in Absence.objects.all(): - user = User.objects.get(id=absence.Target_User_ID.id) - if user in users: - username = user.username - if ( - absence.Target_User_ID not in filtered_users - and name_filtered_by.lower() in username.lower() - ): - filtered_users.append(absence.Target_User_ID) - - # ONLY filtering by username - elif "username" in request.GET: - # Name limit is 50 - name_filtered_by = request.GET["username"][:50] - for user in users: - # same logic as "icontains", searches through users names & finds similarities - if name_filtered_by.lower() in user.username.lower(): - filtered_users.append(user) - - # ONLY filtering by absences - elif "absent" in request.GET: - for absence in Absence.objects.all(): - user = User.objects.get(id=absence.Target_User_ID.id) - if user in users and absence.Target_User_ID not in filtered_users: - filtered_users.append(absence.Target_User_ID) - - # Else, no filtering - else: - filtered_users = users - - return filtered_users - -def retrieve_calendar_data(request:HttpRequest, sortValue): - data = None - r = None - - try: - token = (str(request.user) + "AbsencePlanner").encode() - encryption = hashlib.sha256(token).hexdigest() - r = requests.get(env("TEAM_DATA_URL") + "api/user/teams/?format=json&username={}&sort={}".format(request.user.username, sortValue), headers={"TEAMS-TOKEN": encryption}) - except: - print("API Failed to connect") - - if r is not None and r.status_code == 200: - data = r.json() - - return data - -def retrieve_all_users(request:HttpRequest, data): - users = [] - users.append(request.user) - if data is not None: - for team in data: - for member in team["team"]["members"]: - user = User.objects.filter(username=member["user"]["username"]) - if user.exists() and user not in users: - users.append(user[0]) - - return users - -#JC - Main calendar using API data. -@login_required -def main_calendar( - request:HttpRequest, - month = datetime.datetime.now().strftime("%B"), - year = datetime.datetime.now().year -): - - #JC - Retrieve users profile - try: - userprofile: UserProfile = UserProfile.objects.get(user=request.user) - except: - # TODO Create an error page if the userprofile is not found. - raise NotImplementedError("Invalid Userprofile (No error page)") - - #JC - Retrieve sort value of calendars. - sortValue = None - if request.GET.get("sortBy") is not None: - sortValue = request.GET.get("sortBy") - - #JC - Get names of teams and members in the team. - teams_data = retrieve_calendar_data(request, sortValue) - - #JC - If a month has been selected by the user check if its valid. - date = check_calendar_date(year, month) - if not date: - return redirect('main_calendar') - - date_data = get_date_data(userprofile.region, month, year) - - users = retrieve_all_users(request, teams_data) - - absence_data = get_absence_data(users, 2) - - colour_data = get_colour_data(request) - - weekends = {"Sa": "Sa", "Su": "Su"} - - context = { - **date_data, - **absence_data, - **weekends, - **colour_data, - "team_data": teams_data, - "sort_value": sortValue, - "home_active": True - } - - return render(request, "calendars/calendar.html", context) \ No newline at end of file diff --git a/ap_src/ap_app/errors.py b/ap_src/ap_app/errors.py deleted file mode 100644 index 31f8cd30..00000000 --- a/ap_src/ap_app/errors.py +++ /dev/null @@ -1,22 +0,0 @@ -def handler404(request, exception): - context = {} - response = render(request, "404.html", context=context) - response.status_code = 404 - return response - -def handler500(request): - context = {} - response = render(request, "500.html", context=context) - response.status_code = 500 - return response - -def handler400(request, exception): - context = {} - response = render(request, "400.html", context=context) - response.status_code = 400 - return response - -from django.shortcuts import render - -def my_view(request): - return render(request, "base.html") \ No newline at end of file diff --git a/ap_src/ap_app/fixtures/colours.json b/ap_src/ap_app/fixtures/colours.json deleted file mode 100644 index 01338883..00000000 --- a/ap_src/ap_app/fixtures/colours.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "model": "ap_app.colourscheme", - "pk": 1, - "fields": { - "name": "Bank Holidays", - "default": "#28663a" - } - }, - { - "model": "ap_app.colourscheme", - "pk": 2, - "fields": { - "name": "Weekends", - "default": "#206cab" - } - } -] \ No newline at end of file diff --git a/ap_src/ap_app/fixtures/roles.json b/ap_src/ap_app/fixtures/roles.json index 9687d6f1..3a12c7f4 100644 --- a/ap_src/ap_app/fixtures/roles.json +++ b/ap_src/ap_app/fixtures/roles.json @@ -7,24 +7,24 @@ } }, { - "model": "ap_app.role", - "pk": 3, - "fields": { - "role": "Member" - } - }, - { - "model": "ap_app.role", - "pk": 4, - "fields": { - "role": "Follower" - } - }, - { - "model": "ap_app.role", - "pk": 2, - "fields": { - "role": "Co-Owner" + "model": "ap_app.role", + "pk": 2, + "fields": { + "role": "Member" + } + }, + { + "model": "ap_app.role", + "pk": 3, + "fields": { + "role": "Follower" + } + }, + { + "model": "ap_app.role", + "pk": 4, + "fields": { + "role": "Co-Owner" + } } - } ] \ No newline at end of file diff --git a/ap_src/ap_app/forms.py b/ap_src/ap_app/forms.py index 4d95ab6e..98504f33 100644 --- a/ap_src/ap_app/forms.py +++ b/ap_src/ap_app/forms.py @@ -32,7 +32,7 @@ class Meta: #The description of the team. Can be up to 512 characters long. Is optional. description = forms.CharField( max_length=512, - required=True, + required=False, widget=forms.Textarea(attrs={"class": "", "placeholder": "Team Description","rows":4, "cols":15}), ) diff --git a/ap_src/ap_app/migrations/0004_colourscheme_colordata.py b/ap_src/ap_app/migrations/0004_colourscheme_colordata.py deleted file mode 100644 index 83f25779..00000000 --- a/ap_src/ap_app/migrations/0004_colourscheme_colordata.py +++ /dev/null @@ -1,46 +0,0 @@ -# Generated by Django 4.2.6 on 2024-05-22 13:49 - -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), - ("ap_app", "0003_recurringexception"), - ] - - operations = [ - migrations.CreateModel( - name="ColourScheme", - fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("name", models.CharField(max_length=200)), - ("default", models.CharField(max_length=100)), - ], - ), - migrations.CreateModel( - name="ColorData", - fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), - ("color", models.CharField(max_length=20)), - ("enabled", models.BooleanField(default=True)), - ( - "name", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="colorscheme", - to="ap_app.colourscheme", - ), - ), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/ap_src/ap_app/migrations/0005_rename_name_colordata_scheme.py b/ap_src/ap_app/migrations/0005_rename_name_colordata_scheme.py deleted file mode 100644 index 5916c533..00000000 --- a/ap_src/ap_app/migrations/0005_rename_name_colordata_scheme.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.6 on 2024-05-23 09:41 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("ap_app", "0004_colourscheme_colordata"), - ] - - operations = [ - migrations.RenameField( - model_name="colordata", - old_name="name", - new_name="scheme", - ), - ] diff --git a/ap_src/ap_app/migrations/0006_alter_colourscheme_name_and_more.py b/ap_src/ap_app/migrations/0006_alter_colourscheme_name_and_more.py new file mode 100644 index 00000000..ad3e5d6b --- /dev/null +++ b/ap_src/ap_app/migrations/0006_alter_colourscheme_name_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.6 on 2024-06-10 14:17 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("ap_app", "0005_rename_name_colordata_scheme"), + ] + + operations = [ + migrations.AlterField( + model_name="colourscheme", + name="name", + field=models.CharField(max_length=200, unique=True), + ), + migrations.AlterUniqueTogether( + name="colordata", + unique_together={("user", "scheme")}, + ), + ] diff --git a/ap_src/ap_app/models.py b/ap_src/ap_app/models.py index a593e9ae..bc679397 100644 --- a/ap_src/ap_app/models.py +++ b/ap_src/ap_app/models.py @@ -165,8 +165,6 @@ class UserProfile(models.Model): external_teams = models.BooleanField(default=False) - - def __str__(self): return f"{self.user.username}" @@ -176,22 +174,4 @@ class RecurringException(models.Model): Target_User_ID = models.ForeignKey(User, on_delete=models.CASCADE, related_name="+") User_ID = models.ForeignKey(User, on_delete=models.CASCADE, related_name="recurringexception") Exception_Start = models.DateField(_("Date"), max_length=200, default=now) - Exception_End = models.DateField(_("Date"), max_length=200, default=now) - -class ColourScheme(models.Model): - id = models.AutoField(primary_key=True) - name = models.CharField(max_length=200, unique=True) - default = models.CharField(max_length=100) - -class ColorData(models.Model): - id = models.AutoField(primary_key=True) - scheme = models.ForeignKey(ColourScheme, on_delete=models.CASCADE, related_name="colorscheme") - color = models.CharField(max_length=20) - enabled = models.BooleanField(default=True) - user = models.ForeignKey(User, on_delete=models.CASCADE) - - class Meta: - unique_together = ( - "user", - "scheme", - ) \ No newline at end of file + Exception_End = models.DateField(_("Date"), max_length=200, default=now) \ No newline at end of file diff --git a/ap_src/ap_app/objects.py b/ap_src/ap_app/objects.py deleted file mode 100644 index ab59f71a..00000000 --- a/ap_src/ap_app/objects.py +++ /dev/null @@ -1,42 +0,0 @@ -import pandas as pd -from datetime import timedelta - -from django.contrib import messages -from django.contrib.auth import get_user_model -from django.contrib.auth.decorators import login_required -from django.contrib.auth.forms import UserCreationForm -from django.db.models.functions import Lower -from django.http import HttpResponse, JsonResponse -from django.shortcuts import redirect, render -from django.urls import reverse, reverse_lazy -from django.views.generic import CreateView, UpdateView - -from .forms import * -from .models import ( - UserProfile) - -def find_user_obj(user_to_find): - """Finds & Returns object of 'UserProfile' for a user - \n-param (type)User user_to_find - """ - users = UserProfile.objects.filter(user=user_to_find) - # If cannot find object for a user, than creates on - - if users.count() <= 0: - UserProfile.objects.create(user=user_to_find, accepted_policy=False) - user_found = UserProfile.objects.filter(user=user_to_find)[0] - user_found.edit_whitelist.add(user_to_find) - - # Users object - user_found = UserProfile.objects.filter(user=user_to_find)[0] - - return user_found - - -def obj_exists(user): - """Determines if a user has a 'UserProfile' Object""" - objs = UserProfile.objects.filter(user=user) - if objs.count() == 0: - return False - - return True diff --git a/ap_src/ap_app/static/css/calendar.css b/ap_src/ap_app/static/css/calendar.css deleted file mode 100644 index 6b041420..00000000 --- a/ap_src/ap_app/static/css/calendar.css +++ /dev/null @@ -1,87 +0,0 @@ -.RefreshBox { - pointer-events: none; - text-align: right; - background-color: ghostwhite; - border: 0px; - font-size: 11pt; - width: 24px; - height: 12px; -} -.AbsenceBox { - background-color: #ff0000; - border: 1px solid #ff0000; - border-radius: 12px -} - -.CalendarCell { - min-width: 47px; - max-width: 47px; -} - -.nav-bar-padding { - padding-left: 5%; - padding-right: 5%; -} - -.tooltip { - position: relative; - display: inline-block; -} - -.tooltip .tooltiptext { - visibility: hidden; - width: 120px; - top: 100%; - left: 50%; - margin-left: -60px; - background-color: black; - color: #fff; - text-align: center; - border-radius: 6px; - padding: 5px 0; - position: absolute; - z-index: 5; -} - -.tooltip:hover .tooltiptext { - visibility: visible; -} - -.tooltip:active .tooltiptext { - visibility: hidden; -} - -.button-active { - border: green solid 2px; -} - -.button-inactive { - border: red solid 2px; -} - -.calendar-group { - overflow: auto; - height: 75vh; -} - -.dates-table { - width: 100%; - position: sticky; - top: 0; - z-index: 4; -} - -.blank-cell { - color: black; - width: 6vw; - min-width: 100px; - border: none !important; -} - -.user-cell { - width: 6vw; - min-width: 100px; - background-color: #0e00d6; - color: white; - overflow-x: auto -} \ No newline at end of file diff --git a/ap_src/ap_app/static/css/settings.css b/ap_src/ap_app/static/css/settings.css deleted file mode 100644 index c55d3b30..00000000 --- a/ap_src/ap_app/static/css/settings.css +++ /dev/null @@ -1,38 +0,0 @@ -.collapsible { - /* background-color: #271EA8; */ - color: white; - cursor: pointer; - padding: 18px; - width: 100%; - border: none; - text-align: left; - outline: none; - font-size: 15px; -} - -/*.active, -.collapsible:hover { - background-color: #555; -}*/ - - -.collapsible:after { - content: '\002B'; - color: white; - font-weight: bold; - float: right; - margin-left: 5px; -} - -.active:after { - content: "\2212"; -} - - -.content { - padding: 0 18px; - max-height: 0; - overflow: hidden; - transition: max-height 0.2s ease-out; - background-color: #f1f1f1; -} \ No newline at end of file diff --git a/ap_src/ap_app/static/css/styles.css b/ap_src/ap_app/static/css/styles.css deleted file mode 100644 index 38148f87..00000000 --- a/ap_src/ap_app/static/css/styles.css +++ /dev/null @@ -1,52 +0,0 @@ -/* styles.css */ - -/* The Modal (background) */ -.modal { - display: none; /* Hidden by default */ - position: fixed; /* Stay in place */ - z-index: 5; /* Sit on top */ - left: 0; - top: 0; - width: 100%; /* Full width */ - height: 100%; /* Full height */ - overflow: auto; /* Enable scroll if needed */ - background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ -} - -/* Modal Content */ -.modal-content { - background-color: #fefefe; - margin: 15% auto; /* 15% from the top and centered */ - padding: 20px; - border: 1px solid #888; - width: 80%; /* Could be more or less, depending on screen size */ - text-align: left; -} - -/* The Close Button */ -.close { - color: #aaa; - float: right; - font-size: 28px; - font-weight: bold; -} - -.close:hover, -.close:focus { - color: black; - text-decoration: none; - cursor: pointer; -} - -/* OK Button */ -#alert-ok-button { - background-color: green; - color: white; - border: none; - padding: 10px 20px; - cursor: pointer; -} - -#alert-ok-button:hover { - background-color: darkgreen; -} diff --git a/ap_src/ap_app/static/js/api_calendar.js b/ap_src/ap_app/static/js/api_calendar.js index 0717644f..ff1466e9 100644 --- a/ap_src/ap_app/static/js/api_calendar.js +++ b/ap_src/ap_app/static/js/api_calendar.js @@ -34,16 +34,18 @@ document.addEventListener('click', function(e) { if (absent == "FALSE") { //This is a half day if (e.shiftKey) { - var confirmationPage = document.getElementById("half") - confirmationPage.classList.add("is-active") + document.getElementById("halfDayConfirmation").style.display = "block"; //Morning Clicked - document.getElementById("morningBTN").onclick = function() { + document.getElementById("halfDayMorning").onclick = function() { sendData(username, date, true, "M", "add", "E") - confirmationPage.classList.remove("is-active") + document.getElementById("halfDayConfirmation").style.display = "none"; } - document.getElementById("afternoonBTN").onclick = function() { + document.getElementById("halfDayAfternoon").onclick = function() { sendData(username, date, true, "A", "add", "E") - confirmationPage.classList.remove("is-active") + document.getElementById("halfDayConfirmation").style.display = "none"; + } + document.getElementById("halfDayClose").onclick = function() { + document.getElementById("halfDayConfirmation").style.display = "none"; } } //Full day absence @@ -54,11 +56,17 @@ document.addEventListener('click', function(e) { //Remove an absnece else { var absence_type = e.target.dataset.absenceType; - var confirmationPage = document.getElementById("remove") - confirmationPage.classList.add("is-active") - document.getElementById("removeBTN").onclick = function() { + var confirmationPage = document.getElementById("confirmationBox") + confirmationPage.style.display = "block"; + document.getElementById("cancelAbsece").onclick = function() { + document.getElementById("confirmationBox").style.display = "none"; + } + document.getElementById("absenceClose").onclick = function() { + document.getElementById("confirmationBox").style.display = "none"; + } + document.getElementById("removeAbsence").onclick = function() { sendData(username, date, false, "N", "remove", absence_type) - confirmationPage.classList.remove("is-active") + confirmationPage.style.display = "none"; } } } @@ -66,7 +74,7 @@ document.addEventListener('click', function(e) { }, false) //Toggle clickable calendar on/off -var calendarClickButton = document.getElementById("CalendarClick-Toggle") +var calendarClickButton = document.getElementById("ClickToggle") calendarClickButton.onclick = function() { if (calendarClickToggle == false) { calendarClickButton.style.borderColor = "green"; @@ -87,43 +95,11 @@ function filterTeams(input) { for (cal in calendars) { var calendarID = calendars[cal].id.toString() calendarID = calendarID.replace("title-", "") + console.log(calendarID) if (!calendarID.toUpperCase().includes(input.value.toString().toUpperCase())) { calendars[cal].style.display = "none"; } else { calendars[cal].style.display = ""; } } -} - -function sortTeams(e) { - var url = new URL(window.location.href) - url.searchParams.set("sortBy", e.value) - - window.location.replace(url) -} - -function setDate(e, id) { - data = e.value.split(" ") - month = data[0] - year = data[1] - if (id == 0) { - window.location.replace(window.location.origin + `/calendar/${month}/${year}`) - } else { - window.location.replace(window.location.origin + `/teams/api-calendar/${id}/${month}/${year}`) - } -} - -function openModal(id) { - document.getElementById(id).classList.add("is-active"); -} - -function closeModal(id) { - document.getElementById(id).classList.remove("is-active") -} - -window.onclick = function(event) { - if (event.target.id == 'modalBackground') { - var modalId = event.target.dataset.modalId; - document.getElementById(modalId).classList.remove("is-active") - } } \ No newline at end of file diff --git a/ap_src/ap_app/static/js/profile_update.js b/ap_src/ap_app/static/js/profile_update.js index c1a3cec6..a82a5cff 100644 --- a/ap_src/ap_app/static/js/profile_update.js +++ b/ap_src/ap_app/static/js/profile_update.js @@ -8,9 +8,6 @@ function addUserPermission(token) { }, body: data }) - .then(() => { - location.reload() - }) } function deleteInputtedValue(element) { diff --git a/ap_src/ap_app/static/js/script.js b/ap_src/ap_app/static/js/script.js deleted file mode 100644 index 0dd9af63..00000000 --- a/ap_src/ap_app/static/js/script.js +++ /dev/null @@ -1,35 +0,0 @@ -// script.js - -// Get the modal -var modal = document.getElementById("alertModal"); - -// Get the button that opens the modal -var btn = document.getElementById("question-mark"); - -// Get the element that closes the modal -var span = document.getElementsByClassName("close")[0]; - -// Get the OK button -var okButton = document.getElementById("alert-ok-button"); - -// When the user clicks the button, open the modal -btn.onclick = function() { - modal.style.display = "block"; -} - -// When the user clicks on (x), close the modal -span.onclick = function() { - modal.style.display = "none"; -} - -// When the user clicks the OK button, close the modal -okButton.onclick = function() { - modal.style.display = "none"; -} - -// When the user clicks anywhere outside of the modal, close it -window.onclick = function(event) { - if (event.target == modal) { - modal.style.display = "none"; - } -} diff --git a/ap_src/ap_app/static/js/teams.js b/ap_src/ap_app/static/js/teams.js deleted file mode 100644 index 68102144..00000000 --- a/ap_src/ap_app/static/js/teams.js +++ /dev/null @@ -1,92 +0,0 @@ -var apiURL = document.currentScript.getAttribute("apiURL"); - -function JoinTeam(e, user, redirect) { - var data = JSON.stringify({"username": user, "team": e.id}) - fetch(apiURL + 'api/manage/?method=join', { - method: "post", - body: data, - headers: { - "Content-Type": "application/json", - }, - }) - .then(() => { - if (redirect) { - location.replace(location.origin + "/teams/api-calendar/" + e.id) - } else { - location.reload() - } - }) - .catch(err => { - console.log(err) - }) -} - -function starHover(e) { - if (e.dataset.star == 'false'){ - e.innerHTML="" - e.dataset.star = 'true' - - } -} - -function removeHover(e) { - if (e.dataset.star == 'true'){ - e.innerHTML="" - e.dataset.star = 'false' - } -} - -function favouriteTeam(e, user, id) { - var data = {"username": user, "team": id} - fetch(apiURL + 'api/manage/?method=favourite', { - method:"post", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }) - .then(() => { - location.reload() - }) - .catch(err => { - console.log(err) - }) -} - -function LeaveTeam(e, user, redirect) { - var data = JSON.stringify({"username": user, "team": e.id}) - fetch(apiURL + 'api/manage/?method=leave', { - method: "post", - body: data, - headers: { - "Content-Type": "application/json", - }, - }) - .then(() => { - if (redirect) { - location.replace(location.origin + "/teams") - } else { - location.reload() - } - }) - .catch(err => { - console.log(err) - }) -} - -function DeleteTeam(e) { - var data = JSON.stringify({"id": e.id}) - fetch(apiURL + 'api/teams/?method=delete&format=json', { - method: "post", - body: data, - headers: { - "Content-Type": "application/json", - }, - }) - .then(() => { - location.replace(location.origin + "/teams") - }) - .catch(err => { - console.log(err) - }) -} \ No newline at end of file diff --git a/ap_src/ap_app/teams.py b/ap_src/ap_app/teams.py deleted file mode 100644 index abbe752f..00000000 --- a/ap_src/ap_app/teams.py +++ /dev/null @@ -1,522 +0,0 @@ -#teams_dashboard -#create_team -#join_team -#joining_team_process -#team_invite -#view_invites -#leave_team -#team_cleaner -#team_misc -#team_settings -#edit_team_member_absence -#promote_team_member -#demote_team_member -#remove_team_member -#joining_team_request -#is_member -#is_owner - -import hashlib -import requests -import environ - -from django.contrib import messages -from django.contrib.auth import get_user_model -from django.contrib.auth.decorators import login_required -from django.contrib.auth.forms import UserCreationForm -from django.db.models.functions import Lower -from django.http import HttpResponse, JsonResponse, HttpRequest -from django.shortcuts import redirect, render -from collections import namedtuple - -from .forms import * -from .models import (Absence, Relationship, Role, Team, Status - ) - -from .absences import * - -env = environ.Env() -environ.Env.read_env() - - -def teams_dashboard(request) -> render: - rels = Relationship.objects.order_by(Lower("team__name")).filter( - user=request.user, status=Status.objects.get(status="Active") - ) - invite_rel_count = Relationship.objects.filter( - user=request.user, status=Status.objects.get(status="Invited") - ).count() - - try: - token = (str(request.user) + "AbsencePlanner").encode() - encryption = hashlib.sha256(token).hexdigest() - r = requests.get(env("TEAM_DATA_URL") + "api/user/teams/?username={}".format(request.user.username), headers={"TEAMS-TOKEN": encryption}) - except: - return render( - request, - "teams/dashboard.html", - {"rels": rels, "invite_count": invite_rel_count, "external_teams": False, "teamspage_active": True}) - if r.status_code == 200: - if len(r.json()) == 0 : - external_teams_data = False - else: - external_teams_data = r.json() - else: - external_teams_data = False - return render( - request, - "teams/dashboard.html", - {"rels": rels, "invite_count": invite_rel_count, "external_teams": external_teams_data, "teamspage_active": True, "url": env("TEAM_DATA_URL")}, - ) - - -@login_required -def create_team(request:HttpRequest) -> render: - if request.method == "POST": - form = CreateTeamForm(request.POST) - if form.name_similarity(): - # TODO: write code to tell the user that their team name is similar and to give them - # options to change the team name. - return HttpResponse( - "Debug: Did not create form because the name is too similar to another team name" - ) - - if form.is_valid(): - # form.save() - # # Gets the created team and "Owner" Role and creates a Link between - # # the user and their team - # created_team = Team.objects.get(name=form.cleaned_data["name"]) - # assign_role = Role.objects.get(role="Owner") - # Relationship.objects.create( - # user=request.user, - # team=created_team, - # role=assign_role, - # status=Status.objects.get(status="Active"), - # ) - # return redirect("/teams/", {"message": "Team successfully created."}) - response = requests.post(env("TEAM_DATA_URL") + "api/teams/?format=json", data=request.POST) - if response.status_code == 200: - return redirect("/teams/api-calendar/" + str(response.json()["id"])) - elif response.status_code == 400: - context = {"form": form} - if response.json()["name"] != None: - context["message"] = "That team name already exists" - return render(request, "teams/create_team.html", context=context) - else: - form = CreateTeamForm() - - try: - userprofile: UserProfile = UserProfile.objects.get(user=request.user) - except IndexError: - return redirect("/") - - teams = Team.objects.all() - existing_teams = "" - existing_teams_ids = "" - for i, team in enumerate(teams): - existing_teams += team.name - existing_teams_ids += str(team.id) - if i != len(teams) - 1: - existing_teams += "," - existing_teams_ids += "," - - return render( - request, - "teams/create_team.html", - { - "form": form, - "existing_teams": existing_teams, - "existing_teams_ids": existing_teams_ids, - "api_enabled": userprofile.external_teams, - "api_url": env("TEAM_DATA_URL") + "api/teams/?format=json" - }, - ) - - - -@login_required -def join_team(request) -> render: - """Renders page with all teams the user is not currently in""" - user_teams = [] - all_user_teams = Relationship.objects.filter( - user=request.user, status=Status.objects.get(status="Active") - ) - for teams in all_user_teams: - user_teams.append(teams.team) - all_teams = Team.objects.all().exclude(relationship__user=request.user.id) - all_teams_filtered = [] - - # Filtering by team name - if "teamName" in request.GET: - for team in all_teams: - if request.GET["teamName"].lower() in team.name.lower(): - all_teams_filtered.append(team) - else: - all_teams_filtered = all_teams - - try: - userprofile: UserProfile = UserProfile.objects.get(user=request.user) - except IndexError: - return redirect("/") - - data = None - api_enabled = False - if userprofile.external_teams: - try: - r = requests.get(env("TEAM_DATA_URL") + "api/teams/?username={}".format(request.user.username)) - except: - print("Api failed to load") - if r != None and r.status_code == 200: - data = r.json() - api_enabled = True - - return render( - request, - "teams/join_team.html", - {"all_teams": all_teams_filtered, "joined_teams": user_teams, "api_enabled": api_enabled, "team_data": data, "url": env("TEAM_DATA_URL")}, - ) - - -@login_required -def joining_team_process(request, id, role): - find_team = Team.objects.get(id=id) - find_role = Role.objects.get(role=role) - rels = Relationship.objects.filter( - user=request.user, - role=Role.objects.get(role="Member"), - status=Status.objects.get(status="Active"), - ) - rels2 = Relationship.objects.filter( - user=request.user, - role=Role.objects.get(role="Owner"), - status=Status.objects.get(status="Active"), - ) - existing_rels = Relationship.objects.order_by(Lower("team__name")).filter( - user=request.user, status=Status.objects.get(status="Active") - ) - invite_rel_count = Relationship.objects.filter( - user=request.user, status=Status.objects.get(status="Invited") - ).count() - - if (rels or rels2) and role == "Member": - return render( - request, - "teams/dashboard.html", - {"rels": existing_rels, "invite_count": invite_rel_count, "teamspage_active": True, "message" : "You are already part of one team", "message_type":"is-danger"}, - ) - new_rel = Relationship.objects.create( - user=request.user, - team=find_team, - role=find_role, - status=Status.objects.get(status="Pending"), - ) - if not find_team.private: - Relationship.objects.filter(id=new_rel.id).update( - status=Status.objects.get(status="Active") - ) - #leader = Relationship.objects.get(team=id, role=Role.objects.get(role="Owner")) - #userprofile = UserProfile.objects.get(user=request.user) - #userprofile.edit_whitelist.add(leader.user) - #userprofile.save() - return redirect("dashboard") - - -def team_invite(request, team_id, user_id, role): - find_team = Team.objects.get(id=team_id) - find_user = User.objects.get(id=user_id) - find_role = Role.objects.get(role=role) - - test = Relationship.objects.filter(team=find_team) - - # Boolean determines if viewer is in this team trying to invite others - user_acceptable = False - for rel in test: - if rel.user == request.user and str(rel.role) == "Owner": - user_acceptable = True - break - - if str(request.user.id) != str(user_id) and user_acceptable: - Relationship.objects.create( - user=find_user, - team=find_team, - role=find_role, - status=Status.objects.get(status="Invited"), - ) - return redirect(f"/teams/calendar/{find_team.id}") - # Else user is manipulating the URL making non-allowed invites - (Therefore doesn't create a relationship) - - return redirect("dashboard") - - -@login_required -def view_invites(request): - all_invites = Relationship.objects.filter( - user=request.user, status=Status.objects.get(status="Invited") - ) - return render(request, "teams/invites.html", {"invites": all_invites}) - - -@login_required -def leave_team(request, id): - find_relationship = Relationship.objects.get(id=id) - find_relationship.custom_delete() - team_cleaner(find_relationship) - return redirect("dashboard") - - -def team_cleaner(rel): - """Detects if a team is empty and deletes it if it is.""" - team = Team.objects.get(id=rel.team.id) - all_team_relationships = Relationship.objects.filter(team=team) - if all_team_relationships.count() == 0: - team.delete() - -def is_member(user, team_id) -> bool: - """ Determines if the user is a member of the team before accessing its contents """ - - team = Relationship.objects.filter(team=Team.objects.get(id=team_id)) - - # Boolean determines if viewer is in this team - for rel in team: - if rel.user == user: - return True - - -@login_required -def team_misc(request, id): - """Teams Miscellaneous/Notes page""" - if is_member(user=request.user, team_id=id): - team = Team.objects.get(id=id) - - notes = CreateTeamForm(instance=team) - - # TODO: Add a field to each team with a notes section - (for now it's just the teams description) - - if "value" in request.GET: - team.notes = request.GET["value"] - team.save() - - desc = team.description - notes = team.notes - - return render( - request, "teams/misc.html", {"team": team, "desc": desc, "notes": notes} - ) - return redirect("dashboard") - - -@login_required -def team_settings(request, id): - """Checks to see if user is the owner and renders the Setting page""" - team = Team.objects.get(id=id) - - if is_owner(user=request.user, team_id=id): - all_pending_relations = Relationship.objects.filter( - team=id, status=Status.objects.get(status="Pending") - ) - return render( - request, - "teams/settings.html", - { - "user": request.user, - "team": team, - "pending_rels": all_pending_relations, - "Team_users": team.users, - "member": Role.objects.get(role="Member"), - "coowner": Role.objects.get(role="Co-Owner"), - "follower": Role.objects.get(role="Follower"), - "owner": Role.objects.get(role="Owner"), - }, - ) - return redirect("dashboard") - -def edit_team_member_absence(request, id, user_id) -> render: - from absences import text_rules - """Checks to see if user is the owner and renders the Edit absences page for that user""" - team = Team.objects.get(id=id) - - if is_owner(user=request.user, team_id=id): - all_pending_relations = Relationship.objects.filter( - team=id, status=Status.objects.get(status="Pending") - ) - target_user = User.objects.get(id=user_id) - absences = Absence.objects.filter(Target_User_ID=target_user.id) - rec_absences = text_rules(target_user) - return render( - request, - "teams/edit_absences.html", - { - "team": team, - "user": target_user, - "absences": absences, - "recurring_absences": rec_absences, - }, - ) - return redirect("dashboard") - - -@login_required -def promote_team_member(request, id, user_id): - """Checks to see if user is the owner and renders the Setting page""" - team = Team.objects.get(id=id) - user_relation = Relationship.objects.get(team=id, user=request.user) - if user_relation.role.role != "Owner": - return redirect("dashboard") - target_user = User.objects.get(id=user_id) - all_pending_relations = Relationship.objects.filter( - team=id, status=Status.objects.get(status="Pending") - ) - current_relationship = Relationship.objects.get(team=team, user=target_user) - if current_relationship.role == Role.objects.get(role="Member"): - current_relationship.role = Role.objects.get(role="Co-Owner") - current_relationship.save() - - return redirect(team_settings, team.id) - - -@login_required -def demote_team_member(request, id, user_id): - """Checks to see if user is the owner and renders the Setting page""" - team = Team.objects.get(id=id) - user_relation = Relationship.objects.get(team=id, user=request.user) - if user_relation.role.role != "Owner": - return redirect("dashboard") - target_user = User.objects.get(id=user_id) - all_pending_relations = Relationship.objects.filter( - team=id, status=Status.objects.get(status="Pending") - ) - current_relationship = Relationship.objects.get(team=team, user=target_user) - if current_relationship.role == Role.objects.get(role="Co-Owner"): - current_relationship.role = Role.objects.get(role="Member") - current_relationship.save() - - return redirect(team_settings, team.id) - -@login_required -def remove_team_member(request, id, user_id): - """Removes a member from a team.""" - team = Team.objects.get(id=id) - user_relation = Relationship.objects.get(team=id, user=request.user) - if user_relation.role.role != "Owner": - return redirect("dashboard") #Checks if the user is the owner and redirects to the dashboard if they aren't - target_user = User.objects.get(id=user_id) #Gets the user to be removed - target_relation = Relationship.objects.get(team=team, user=target_user) #Gets the target's relationship to the team - target_relation.custom_delete() #Deletes the relationship from the team, removing the user - - return redirect(team_settings, team.id) #Redirects user back to the settings page - - -def joining_team_request(request, id, response): - - find_rel = Relationship.objects.get(id=id) - - - if response == "accepted": - state_response = Status.objects.get(status="Active") - Relationship.objects.filter(id=find_rel.id).update(status=state_response) - return redirect("team_settings", find_rel.team.id) - elif response == "nonactive": - return redirect("leave_team", id) - return redirect("dashboard") - -#JC - Add an absence via the form -def manual_add(request:HttpRequest) -> render: - #JC - POST request - if request.method == "POST": - form = AbsenceForm( - request.POST, - user=request.user, - initial={ - "start_date": datetime.datetime.now(), - "end_date": lambda: datetime.datetime.now().date() + datetime.timedelta(days=1) - } - ) - form.fields["user"].queryset = UserProfile.objects.filter( - edit_whitelist__in=[request.user] - ) - - #JC - Create absence - if form.is_valid(): - absence = Absence() - absence.absence_date_start = form.cleaned_data["start_date"] - absence.absence_date_end = form.cleaned_data["end_date"] - absence.User_ID = request.user - absence.Target_User_ID = form.cleaned_data["user"].user - - #JC - Check if the dates overlap with an existing absence. - valid = True - Range = namedtuple('Range', ['start', 'end']) - r1 = Range(start=absence.absence_date_start, end=absence.absence_date_end) - for x in Absence.objects.filter(Target_User_ID=form.cleaned_data["user"].user.id): - r2 = Range(start=x.absence_date_start, end=x.absence_date_end) - delta = (min(r1.end, r2.end) -max(r1.start, r2.start)).days + 1 - overlapp = max(0, delta) - if overlapp == 1: - valid = False - - if valid: - absence.save() - return redirect("/") - else: - return render(request, "ap_app/add_absence.html", { - "form": form, - "message": "The absence conflicts with an existing absence", - "message_type": "is-danger" - }) - - #JC - GET request - else: - form = AbsenceForm(user=request.user) - #JC - Allow users to edit others absence if they have permission - form.fields["user"].queryset = UserProfile.objects.filter( - edit_whitelist__in=[request.user] - ) - - content = {"form": form} - return render(request, "ap_app/add_absence.html", content) - -def is_owner(user, team_id) -> bool: - """ Determines if the user is an owner of the team """ - - user_relation = Relationship.objects.get(team=team_id, user=user) - return (user_relation.role.role == "Owner") - - -def edit_api_data(userprofile, id): - data = None - if userprofile.external_teams: - try: - r = requests.get(env("TEAM_DATA_URL") + "api/members/?id={}".format(id)) - data = r.json() - except: - raise NotImplementedError("Could not find API (No error page)") - - if r.status_code != 200: - raise NotImplementedError("Invalid team name (No error page)") - else: - raise NotImplementedError("The API setting is not enabled in your profile. (No error page)") - - return data - -#This page allows owners of a team to modify differnt properties of a team. -#Links to: teams/edit_team.html -@login_required -def edit_team(request:HttpRequest, id): - - if not id: - return JsonResponse({"Error": "Team name not found"}) - - userprofile: UserProfile = UserProfile.objects.get(user=request.user) - - if request.method == "POST": - response = requests.post(env("TEAM_DATA_URL") + "api/teams/?method=edit&format=json", data=request.POST) - if response.status_code != 200: - print(response.json()) - - api_data = edit_api_data(userprofile, id) - if api_data == None: - raise ValueError("Invalid API Data") - - roles = Role.objects.all() - - return render(request, "teams/edit_team.html", context={"team": api_data[0], "roles": roles, "url": env("TEAM_DATA_URL")}) \ No newline at end of file diff --git a/ap_src/ap_app/templatetags/check_permissions.py b/ap_src/ap_app/templatetags/check_permissions.py index a3fd91c5..aa892f36 100644 --- a/ap_src/ap_app/templatetags/check_permissions.py +++ b/ap_src/ap_app/templatetags/check_permissions.py @@ -4,13 +4,10 @@ register = template.Library() -@register.simple_tag +@register.filter(name="check_permissions") def check_permissions(user, active_user): - perm_list = UserProfile.objects.filter(user__username=user["user"]["username"]) - if perm_list.exists(): - if active_user in perm_list[0].edit_whitelist.all(): - return True - else: - return False - - return False \ No newline at end of file + perm_list = UserProfile.objects.filter(user=user)[0].edit_whitelist.all() + if active_user in perm_list: + return True + else: + return False \ No newline at end of file diff --git a/ap_src/ap_app/urls.py b/ap_src/ap_app/urls.py index 632966ba..afa99399 100644 --- a/ap_src/ap_app/urls.py +++ b/ap_src/ap_app/urls.py @@ -1,20 +1,17 @@ from django.urls import path -from . import views, teams, absences, calendarview +from . import views js_info_dict = { 'packages' : ('recurrence', ), } from django.conf.urls import handler404, handler500 -from django.urls import re_path -from django.views.static import serve -from django.conf import settings urlpatterns = [ path("", views.index, name="index"), path('signup/', views.SignUpView.as_view(), name='signup'), - path("calendar/", views.main_calendar, name="all_Calendar"), - path("calendar//", views.main_calendar, name="all_calendar"), + path("calendar/0/", views.all_calendar, name="all_Calendar"), + path("calendar/0//", views.all_calendar, name="all_calendar"), path("teams/", views.teams_dashboard, name="dashboard"), path("teams/create", views.create_team, name="create_team"), path("teams/invite/", views.view_invites, name="view_invites"), @@ -23,16 +20,16 @@ path("teams/join//", views.joining_team_process, name="join_team_2"), path("teams/join/apply//", views.joining_team_request, name="joining_team_request"), path("teams/leave/", views.leave_team, name="leave_team"), + path("teams/join_requests//", views.join_requests, name="join_requests"), path("teams/settings//", views.team_settings, name="team_settings"), - path("teams/api-calendar/", views.api_team_calendar, name="api_team_calendar"), - path("teams/api-calendar///", views.api_team_calendar, name="api_team_calendar"), + path("teams/calendar/", views.team_calendar, name="Calendar"), + path("teams/calendar///", views.team_calendar, name="calendar"), path("teams/settings//", views.edit_team_member_absence, name="edit_team_member_absence"), path("teams/settings/promote//", views.promote_team_member, name="promote_team_member"), path("teams/settings/demote//", views.demote_team_member, name="demote_team_member"), path("teams/settings/remove//", views.remove_team_member, name="remove_team_member"), path("teams/misc/", views.team_misc, name="misc"), - path("teams/edit/", views.edit_team, name="edit_team"), - path("absence/add", teams.manual_add, name="add"), + path("absence/add", views.manual_add, name="add"), path("absence/add_recurring", views.add_recurring, name="add_recurring"), path("profile/", views.profile_page, name="profile"), path("privacy/", views.privacy_page, name="privacy"), @@ -45,17 +42,14 @@ path("absence/edit_recurring/", views.edit_recurring_absences, name="recurring_absence_edit"), path("absence/click_add", views.click_add, name="absence_click_add"), path("absence/click_remove", views.click_remove, name="absence_click_remove"), - path("profile/settings", views.profile_settings, name="profile_settings"), + path("profile/settings", views.profile_settings, name="profile settings"), path("profile/settings/add-user", views.add_user, name="add-user"), path("profile/settings/set-region", views.set_region, name="set-region"), - path("profile/settings/update-colour", views.update_colour, name="update-colour"), path("calendar/set_month",views.set_calendar_month, name="set_month"), - path("main_calendar", views.main_calendar, name="main_calendar"), - path("main_calendar//", views.main_calendar, name="main_calendar"), - re_path(r'^static/(?P.*)$', serve,{'document_root': settings.STATIC_ROOT}) #This lets Django find the CSS files when debug is set to false + path("calendar/1/", views.api_calendar_view, name="api_calendar"), + path("calendar/1//", views.api_calendar_view, name="api_calendar") +] -] - -handler404 = 'ap_app.errors.handler404' -handler500 = 'ap_app.errors.handler500' -handler400 = 'ap_app.errors.handler400' +handler404 = 'ap_app.views.handler404' +handler500 = 'ap_app.views.handler500' +handler400 = 'ap_app.views.handler400' diff --git a/ap_src/ap_app/views.py b/ap_src/ap_app/views.py index 21f3a08e..4d2bc327 100644 --- a/ap_src/ap_app/views.py +++ b/ap_src/ap_app/views.py @@ -1,14 +1,6 @@ -#index -#privacy_page -#add -#details_page -#deleteuser -#profile_settings -#add_user -#get_region_data -#set_region -#click_add -#click_remove +# TODO: Review functions and variables names and ensure that they +# .. have meaningful names that represent what they do. + import calendar import datetime @@ -18,7 +10,6 @@ import pandas as pd import requests import environ -import hashlib from datetime import timedelta from django.contrib import messages @@ -30,22 +21,21 @@ from django.shortcuts import redirect, render from django.urls import reverse, reverse_lazy from django.views.generic import CreateView, UpdateView +from django.contrib.auth.models import User -from .teams import * -from .objects import * -from .teams import * -from .calendarview import * +from collections import namedtuple from .forms import * from .models import (Absence, RecurringAbsences, Relationship, Role, Team, - UserProfile, ColourScheme, ColorData) + UserProfile, Status, RecurringException) -User = get_user_model() +env = environ.Env() +environ.Env.read_env() -def index(request) -> render: - """Branched view. \n - IF NOT Logged in: returns the home page \n +def index(request): + """Branched view. + IF NOT Logged in: returns the home page ELSE: returns the calendar page""" # If its the first time a user is logging in, will create an object of UserProfile model for that user. @@ -56,7 +46,7 @@ def index(request) -> render: else: user = UserProfile.objects.filter(user=request.user)[0] user.edit_whitelist.add(request.user) - # Until the accepted_policy field is checked, the user will keep being redirected to the policy page to accept + if not user.accepted_policy: return privacy_page(request, to_accept=True) @@ -64,12 +54,12 @@ def index(request) -> render: if request.user.is_authenticated: user = UserProfile.objects.get(user=request.user) user.edit_whitelist.add(request.user) - return main_calendar(request) + return all_calendar(request) + return render(request, "ap_app/index.html") -def privacy_page(request, to_accept=False) -> render: - # If true, the user accepted the policy +def privacy_page(request, to_accept=False): if request.method == "POST": user = find_user_obj(request.user) user.accepted_policy = True @@ -77,243 +67,420 @@ def privacy_page(request, to_accept=False) -> render: # If the user has been redirected from home page to accepted policy elif to_accept: - return render( - request, "registration/accept_policy.html", {"form": AcceptPolicyForm()} - ) + return render(request, "registration/accept_policy.html", {"form": AcceptPolicyForm()}) else: # Viewing general policy page - (Without the acceptancy form) return render(request, "ap_app/privacy.html") - return main_calendar(request) + + return all_calendar(request) + class SignUpView(CreateView): form_class = UserCreationForm success_url = reverse_lazy("login") template_name = "registration/signup.html" + @login_required -def example(request): +def teams_dashboard(request): + rels = Relationship.objects.order_by(Lower("team__name")).filter( + user=request.user, status=Status.objects.get(status="Active") + ) + invite_rel_count = Relationship.objects.filter( + user=request.user, status=Status.objects.get(status="Invited") + ).count() + + try: + r = requests.get("http://localhost:8000/api/teams/?username={}".format(request.user.username)) + if r.status_code == 200: + external_teams_data = r.json() if len(r.json()) > 0 else False + else: + external_teams_data = False + except requests.exceptions.RequestException: + external_teams_data = False + + return render( + request, + "teams/dashboard.html", + {"rels": rels, "invite_count": invite_rel_count, "external_teams": external_teams_data, "teamspage_active": True}, + ) + +@login_required +def create_team(request): if request.method == "POST": - form = AbsenceForm( - request.POST, - user=request.user, - initial={ - "start_date": datetime.datetime.now(), - "end_date": lambda: datetime.datetime.now().date() - + datetime.timedelta(days=1), - }, - ) - form.fields["user"].queryset = UserProfile.objects.filter( - edit_whitelist__in=[request.user] - ) + form = CreateTeamForm(request.POST) + if form.name_similarity(): + return HttpResponse("Debug: Did not create form because the name is too similar to another team name") if form.is_valid(): - obj = Absence() - obj.absence_date_start = request.POST.get("start_date") - obj.absence_date_end = request.POST.get("end_date") - obj.absence_date_start = form.cleaned_data["start_date"] - obj.absence_date_end = form.cleaned_data["end_date"] - obj.request_accepted = False # TODO - obj.User_ID = request.user - obj.Target_User_ID = form.cleaned_data["user"].user - message = "Absence successfully created" - msg_type = "is-success" - absence_valid = True - for x in Absence.objects.filter(User_ID=request.user.id): - if (obj.absence_date_start >= x.absence_date_start and obj.absence_date_end <= x.absence_date_end) \ - or x.absence_date_start == obj.absence_date_end or x.absence_date_end == x.absence_date_start: - message = "Absence already created" - msg_type = "is-danger" - absence_valid = False - if absence_valid: - obj.save() - return render( - request, - "ap_app/add_absence.html", - { - "form": form, - "message": message, - "message_type": msg_type, - "add_absence_active": True, - }, + form.save() + # Gets the created team and "Owner" Role and creates a Link between + # the user and their team + created_team = Team.objects.get(name=form.cleaned_data["name"]) + assign_role = Role.objects.get(role="Owner") + Relationship.objects.create( + user=request.user, + team=created_team, + role=assign_role, + status=Status.objects.get(status="Active"), ) - # redirect to success page + return redirect("/teams/", {"message": "Team successfully created."}) else: - form = AbsenceForm(user=request.user) - form.fields["user"].queryset = UserProfile.objects.filter( - edit_whitelist__in=[request.user] + form = CreateTeamForm() + + teams = Team.objects.all() + existing_teams = ",".join(team.name for team in teams) + existing_teams_ids = ",".join(str(team.id) for team in teams) + + return render( + request, + "teams/create_team.html", + { + "form": form, + "existing_teams": existing_teams, + "existing_teams_ids": existing_teams_ids, + }, + ) + + +@login_required +def join_team(request) -> render: + """Renders page with all teams the user is not currently in""" + user_teams = [] + all_user_teams = Relationship.objects.filter( + user=request.user, status=Status.objects.get(status="Active") + ) + for teams in all_user_teams: + user_teams.append(teams.team) + all_teams = Team.objects.all().exclude(relationship__user=request.user.id) + all_teams_filtered = [] + + # Filtering by team name + if "teamName" in request.GET: + for team in all_teams: + if request.GET["teamName"].lower() in team.name.lower(): + all_teams_filtered.append(team) + else: + all_teams_filtered = all_teams + + return render( + request, + "teams/join_team.html", + {"all_teams": all_teams_filtered, "joined_teams": user_teams}, + ) + + +@login_required +def joining_team_process(request, id, role): + find_team = Team.objects.get(id=id) + find_role = Role.objects.get(role=role) + rels = Relationship.objects.filter( + user=request.user, + role=Role.objects.get(role="Member"), + status=Status.objects.get(status="Active"), + ) + rels2 = Relationship.objects.filter( + user=request.user, + role=Role.objects.get(role="Owner"), + status=Status.objects.get(status="Active"), + ) + existing_rels = Relationship.objects.order_by(Lower("team__name")).filter( + user=request.user, status=Status.objects.get(status="Active") + ) + invite_rel_count = Relationship.objects.filter( + user=request.user, status=Status.objects.get(status="Invited") + ).count() + + if (rels or rels2) and role == "Member": + return render( + request, + "teams/dashboard.html", + {"rels": existing_rels, "invite_count": invite_rel_count, "teamspage_active": True, "message" : "You are already part of one team", "message_type":"is-danger"}, + ) + new_rel = Relationship.objects.create( + user=request.user, + team=find_team, + role=find_role, + status=Status.objects.get(status="Pending"), + ) + if not find_team.private: + Relationship.objects.filter(id=new_rel.id).update( + status=Status.objects.get(status="Active") ) + #leader = Relationship.objects.get(team=id, role=Role.objects.get(role="Owner")) + #userprofile = UserProfile.objects.get(user=request.user) + #userprofile.edit_whitelist.add(leader.user) + #userprofile.save() + return redirect("dashboard") + + +def team_invite(request, team_id, user_id, role): + find_team = Team.objects.get(id=team_id) + find_user = User.objects.get(id=user_id) + find_role = Role.objects.get(role=role) + + test = Relationship.objects.filter(team=find_team) + + # Boolean determines if viewer is in this team trying to invite others + user_acceptable = False + for rel in test: + if rel.user == request.user and str(rel.role) == "Owner": + user_acceptable = True + break + + if str(request.user.id) != str(user_id) and user_acceptable: + Relationship.objects.create( + user=find_user, + team=find_team, + role=find_role, + status=Status.objects.get(status="Invited"), + ) + return redirect(f"/teams/calendar/{find_team.id}") + # Else user is manipulating the URL making non-allowed invites - (Therefore doesn't create a relationship) + + return redirect("dashboard") - content = {"form": form, "add_absence_active": True} - return render(request, "ap_app/add_absence.html", content) @login_required -def details_page(request) -> render: - """returns details web page""" - # TODO: get employee details and add them to context - context = {"employee_dicts": ""} - return render(request, "ap_app/Details.html", context) +def view_invites(request): + all_invites = Relationship.objects.filter( + user=request.user, status=Status.objects.get(status="Invited") + ) + return render(request, "teams/invites.html", {"invites": all_invites}) + @login_required -def deleteuser(request): - """delete a user account""" - if request.method == "POST": - delete_form = DeleteUserForm(request.POST, instance=request.user) - user = request.user - user.delete() - messages.info(request, "Your account has been deleted.") - return redirect("index") - else: - delete_form = DeleteUserForm(instance=request.user) +def leave_team(request, id): + find_relationship = Relationship.objects.get(id=id) + find_relationship.custom_delete() + team_cleaner(find_relationship) + return redirect("dashboard") - context = {"delete_form": delete_form} - return render(request, "registration/delete_account.html", context) +def team_cleaner(rel): + """Detects if a team is empty and deletes it if it is.""" + team = Team.objects.get(id=rel.team.id) + all_team_relationships = Relationship.objects.filter(team=team) + if all_team_relationships.count() == 0: + team.delete() + @login_required -def profile_settings(request:HttpRequest) -> render: - """returns the settings page""" +def team_misc(request, id): + """Teams Miscellaneous/Notes page""" + if is_member(user=request.user, team_id=id): + team = Team.objects.get(id=id) - if len(request.POST) > 0: - if request.POST.get("firstName") != "" and request.POST.get("firstName") != request.user.first_name: - request.user.first_name = request.POST.get("firstName") - request.user.save() - if request.POST.get("lastName") != "" and request.POST.get("lastName") != request.user.last_name: - request.user.last_name = request.POST.get("lastName") - request.user.save() - if request.POST.get("email") != request.user.email: - request.user.email = request.POST.get("email") - request.user.save() + notes = CreateTeamForm(instance=team) - try: - userprofile: UserProfile = UserProfile.objects.filter(user=request.user)[0] - except IndexError: - # TODO Create error page - return redirect("/") - - if len(request.POST) > 0: - region = request.POST.get("region") - region_code = pycountry.countries.get(name=region).alpha_2 - if region_code != userprofile.region: - userprofile.region = region_code - if request.POST.get("privacy") == None: - userprofile.privacy = False - elif request.POST.get("privacy") == "on": - userprofile.privacy = True + # TODO: Add a field to each team with a notes section - (for now it's just the teams description) - if request.POST.get("teams") == None: - userprofile.external_teams = False - elif request.POST.get("teams") == "on": - userprofile.external_teams = True - - userprofile.save() - - country_data = get_region_data() - country_name = pycountry.countries.get(alpha_2=userprofile.region).name + if "value" in request.GET: + team.notes = request.GET["value"] + team.save() - colour_data = [] - for scheme in ColourScheme.objects.all(): - colour = {} - data = ColorData.objects.filter(user=request.user, scheme=scheme) - colour["name"] = scheme.name - if data.exists(): - colour["colour"] = data[0].color - colour["enabled"] = data[0].enabled - else: - colour["colour"] = scheme.default - colour["enabled"] = True + desc = team.description + notes = team.notes - colour_data.append(colour) + return render( + request, "teams/misc.html", {"team": team, "desc": desc, "notes": notes} + ) + return redirect("dashboard") - privacy_status = userprofile.privacy - teams_status = userprofile.external_teams - context = {"userprofile": userprofile, "data_privacy_mode": privacy_status, "external_teams": teams_status, - "current_country": country_name, **country_data, "colours": colour_data} - return render(request, "ap_app/settings.html", context) -def update_colour(request:HttpRequest): - if request.method == "POST": - default = ColourScheme.objects.get(name=request.POST["name"]) - data = ColorData.objects.filter(user=request.user, scheme__name=request.POST["name"]) - if data.exists(): - update_data = ColorData.objects.get(id=data[0].id) - if request.POST["enabled"] == 'True': - update_data.enabled = True - else: - update_data.enabled = False - update_data.color = request.POST["colour"] - update_data.save() - else: - print(request.POST) - if request.POST["colour"] != default.default or request.POST["enabled"] != 'True': - print("Created colour data") - newData = ColorData() - if request.POST["enabled"] == 'True': - newData.enabled = True - else: - newData.enabled = False - newData.scheme = default - newData.color = request.POST["colour"] - newData.user = request.user - newData.save() +@login_required +def join_requests(request, id): + team = Team.objects.get(id=id) - return redirect('profile_settings') + if is_owner(user=request.user, team_id=id): + all_pending_relations = Relationship.objects.filter( + team=id, status=Status.objects.get(status="Pending") + ) + return render( + request, + "teams/join_requests.html", + { + "user": request.user, + "team": team, + "pending_rels": all_pending_relations, + "Team_users": team.users, + "member": Role.objects.get(role="Member"), + "coowner": Role.objects.get(role="Co-Owner"), + "follower": Role.objects.get(role="Follower"), + "owner": Role.objects.get(role="Owner"), + }, + ) + return redirect("dashboard") @login_required -def add_user(request) -> render: - data=json.loads(request.body) - try: - userprofile: UserProfile = UserProfile.objects.filter(user=request.user)[0] - except IndexError: - # TODO Create error page - return redirect("/") +def team_settings(request, id): + """Checks to see if user is the owner and renders the Setting page""" + team = Team.objects.get(id=id) + + if is_owner(user=request.user, team_id=id): + all_pending_relations = Relationship.objects.filter( + team=id, status=Status.objects.get(status="Pending") + ) + return render( + request, + "teams/settings.html", + { + "user": request.user, + "team": team, + "pending_rels": all_pending_relations, + "Team_users": team.users, + "member": Role.objects.get(role="Member"), + "coowner": Role.objects.get(role="Co-Owner"), + "follower": Role.objects.get(role="Follower"), + "owner": Role.objects.get(role="Owner"), + }, + ) + return redirect("dashboard") - if request.method == "POST": - username = data["username"] +def edit_team_member_absence(request, id, user_id) -> render: + """Checks to see if user is the owner and renders the Edit absences page for that user""" + team = Team.objects.get(id=id) + + if is_owner(user=request.user, team_id=id): + all_pending_relations = Relationship.objects.filter( + team=id, status=Status.objects.get(status="Pending") + ) + target_user = User.objects.get(id=user_id) + absences = Absence.objects.filter(Target_User_ID=target_user.id) + rec_absences = text_rules(target_user) + return render( + request, + "teams/edit_absences.html", + { + "team": team, + "user": target_user, + "absences": absences, + "recurring_absences": rec_absences, + }, + ) + return redirect("dashboard") - try: - user = User.objects.get(username=username) - except: - # TODO Create error page - return redirect("/") - userprofile.edit_whitelist.add(user) - user.save() +@login_required +def promote_team_member(request, id, user_id): + """Checks to see if user is the owner and renders the Setting page""" + team = Team.objects.get(id=id) + user_relation = Relationship.objects.get(team=id, user=request.user) + if user_relation.role.role != "Owner": + return redirect("dashboard") + target_user = User.objects.get(id=user_id) + all_pending_relations = Relationship.objects.filter( + team=id, status=Status.objects.get(status="Pending") + ) + current_relationship = Relationship.objects.get(team=team, user=target_user) + if current_relationship.role == Role.objects.get(role="Member"): + current_relationship.role = Role.objects.get(role="Co-Owner") + current_relationship.save() - return redirect("/profile/settings") + return redirect(team_settings, team.id) -def get_region_data(): - data = {} - data["countries"] = [] - for country in list(pycountry.countries): - try: - holidays.country_holidays(country.alpha_2) - data["countries"].append(country.name) - except: - pass - - data["countries"] = sorted(data["countries"]) - return data +@login_required +def demote_team_member(request, id, user_id): + """Checks to see if user is the owner and renders the Setting page""" + team = Team.objects.get(id=id) + user_relation = Relationship.objects.get(team=id, user=request.user) + if user_relation.role.role != "Owner": + return redirect("dashboard") + target_user = User.objects.get(id=user_id) + all_pending_relations = Relationship.objects.filter( + team=id, status=Status.objects.get(status="Pending") + ) + current_relationship = Relationship.objects.get(team=team, user=target_user) + if current_relationship.role == Role.objects.get(role="Co-Owner"): + current_relationship.role = Role.objects.get(role="Member") + current_relationship.save() + + return redirect(team_settings, team.id) @login_required -def set_region(request): +def remove_team_member(request, id, user_id): + """Removes a member from a team.""" + team = Team.objects.get(id=id) + user_relation = Relationship.objects.get(team=id, user=request.user) + if user_relation.role.role != "Owner": + return redirect("dashboard") #Checks if the user is the owner and redirects to the dashboard if they aren't + target_user = User.objects.get(id=user_id) #Gets the user to be removed + target_relation = Relationship.objects.get(team=team, user=target_user) #Gets the target's relationship to the team + target_relation.custom_delete() #Deletes the relationship from the team, removing the user - try: - userporfile: UserProfile = UserProfile.objects.filter(user=request.user)[0] - except IndexError: - # TODO Create error page - return redirect("/") + return redirect(team_settings, team.id) #Redirects user back to the settings page + + +def joining_team_request(request, id, response): + find_rel = Relationship.objects.get(id=id) + + + if response == "accepted": + state_response = Status.objects.get(status="Active") + Relationship.objects.filter(id=find_rel.id).update(status=state_response) + return redirect("team_settings", find_rel.team.id) + elif response == "nonactive": + return redirect("leave_team", id) + return redirect("dashboard") + +#JC - Add an absence via the form +@login_required +def manual_add(request:HttpRequest) -> render: + #JC - POST request if request.method == "POST": - region = request.POST.get("regions") - region_code = pycountry.countries.get(name=region).alpha_2 - if (region_code != userporfile.region): - userporfile.region = region_code - userporfile.save() + form = AbsenceForm( + request.POST, + user=request.user, + initial={ + "start_date": datetime.datetime.now(), + "end_date": lambda: datetime.datetime.now().date() + datetime.timedelta(days=1) + } + ) + form.fields["user"].queryset = UserProfile.objects.filter( + edit_whitelist__in=[request.user] + ) - return redirect("/profile/settings") + #JC - Create absence + if form.is_valid(): + absence = Absence() + absence.absence_date_start = form.cleaned_data["start_date"] + absence.absence_date_end = form.cleaned_data["end_date"] + absence.User_ID = request.user + absence.Target_User_ID = form.cleaned_data["user"].user -#JC - Calendar view using the API + #JC - Check if the dates overlap with an existing absence. + valid = True + Range = namedtuple('Range', ['start', 'end']) + r1 = Range(start=absence.absence_date_start, end=absence.absence_date_end) + for x in Absence.objects.filter(Target_User_ID=form.cleaned_data["user"].user.id): + r2 = Range(start=x.absence_date_start, end=x.absence_date_end) + delta = (min(r1.end, r2.end) -max(r1.start, r2.start)).days + 1 + overlapp = max(0, delta) + if overlapp == 1: + valid = False + + if valid: + absence.save() + return redirect("/") + else: + return render(request, "ap_app/add_absence.html", { + "form": form, + "message": "The absence conflicts with an existing absence", + "message_type": "is-danger" + }) + + #JC - GET request + else: + form = AbsenceForm(user=request.user) + #JC - Allow users to edit others absence if they have permission + form.fields["user"].queryset = UserProfile.objects.filter( + edit_whitelist__in=[request.user] + ) + + content = {"form": form} + return render(request, "ap_app/add_absence.html", content) #Add an absence when clicking on the calendar @login_required @@ -365,7 +532,7 @@ def non_connected(): absence.save() else: absence = non_connected() - + elif date - timedelta(days=1) in Absence.objects.filter(Target_User_ID__username=json_data["username"]).values_list("absence_date_start", flat=True): a = Absence.objects.filter(Target_User_ID__username=json_data["username"], absence_date_start=date-timedelta(days=1))[0] if a.half_day == "NORMAL": @@ -406,7 +573,6 @@ def non_connected(): return JsonResponse({}) else: return HttpResponse('404') - @login_required def click_remove(request): if request.method == "POST": @@ -466,3 +632,903 @@ def click_remove(request): return JsonResponse({"start_date": data["date"]}) else: return HttpResponse("404") + +@login_required +def add_recurring(request) -> render: + if request.method == "POST": + form = RecurringAbsencesForm(request.POST) + form2 = TargetUserForm(request.POST, target_user=request.user) + form2.fields["target_user"].queryset = UserProfile.objects.filter( + edit_whitelist__in=[request.user] + ) + rule = str(form["Recurrences"].value()) + + + if not ("DAILY" in rule or "BY" in rule): + content = { + "form": form, + "form2": form2, + "message": "Must select a day/month", + } + return render(request, "ap_app/add_recurring_absence.html", content) + + if form2.is_valid(): + RecurringAbsences.objects.create( + Recurrences=form["Recurrences"].value(), + Target_User_ID=form2.cleaned_data["target_user"].user, + User_ID=request.user, + ) + return redirect("index") + else: + form = RecurringAbsencesForm() + form2 = TargetUserForm(target_user=request.user) + form2.fields["target_user"].queryset = UserProfile.objects.filter( + edit_whitelist__in=[request.user] + ) + + content = {"form": form, "form2": form2} + return render(request, "ap_app/add_recurring_absence.html", content) + + +@login_required +def details_page(request) -> render: + """returns details web page""" + # TODO: get employee details and add them to context + context = {"employee_dicts": ""} + return render(request, "ap_app/Details.html", context) + + +def get_date_data( + region, + month=datetime.datetime.now().strftime("%B"), + year=datetime.datetime.now().year, +): + # uses a dictionary to get all the data needed for the context + # and concatenates it to form the full context with other dictionaries + data = {} + data["current_year"] = datetime.datetime.now().year + data["current_month"] = datetime.datetime.now().strftime("%B") + data["current_month_num"] = datetime.datetime.now().month + data["today"] = datetime.datetime.now().day + data["year"] = year + data["month"] = month + data["next_current_year"] = datetime.datetime.now().year + 1 + data["next_second_year"] = datetime.datetime.now().year + 2 + + start_date, end_date = "2022-07-03", "2024-07-03" + + data["month_list"] = pd.period_range(start=start_date, end=end_date, freq='M') + data["month_list"] = [month.strftime("%B %Y") for month in data["month_list"]] + + data["selected_date"] = datetime.date(int(data["year"]), datetime.datetime.strptime(month, "%B").month, 1).strftime("%B %Y") + + data["day_range"] = range( + 1, + calendar.monthrange( + data["year"], datetime.datetime.strptime(month, "%B").month + )[1] + + 1, + ) + data["day_range_num"] = len(list(data["day_range"])) + 1 + data["month_num"] = datetime.datetime.strptime(month, "%B").month + + data["previous_month"] = "December" + data["next_month"] = "January" + data["previous_year"] = year - 1 + data["next_year"] = year + 1 + + # as the month number resets every year try and except statements + # have to be used as at the end and start of a year + # the month cannot be calculated by adding or subtracting 1 + # as 13 and 0 are not datetime month numbers + try: + data["next_month"] = datetime.datetime.strptime( + str((datetime.datetime.strptime(data["month"], "%B")).month + 1), "%m" + ).strftime("%B") + + except ValueError: + pass + try: + data["previous_month"] = datetime.datetime.strptime( + str((datetime.datetime.strptime(data["month"], "%B")).month - 1), "%m" + ).strftime("%B") + except ValueError: + pass + + # calculating which days are weekends to mark them easier in the html + data["days_name"] = [] + for day in data["day_range"]: + date = f"{day} {month} {year}" + date = datetime.datetime.strptime(date, "%d %B %Y") + date = date.strftime("%A")[0:2] + data["days_name"].append(date) + + data["weekend_list"] = [] + for day in data["day_range"]: + date = f"{day} {month} {year}" + date = datetime.datetime.strptime(date, "%d %B %Y") + date = date.strftime("%A")[0:2] + if (date == "Sa" or date == "Su"): + data["weekend_list"].append(day) + + data["bank_hol"] = [] + for h in holidays.country_holidays(region, years=year).items(): + if (h[0].month == data["month_num"]): + data["bank_hol"].append(h[0].day) + + return data + + + + +def get_absence_data(users, user_type): + data = {} + absence_content = [] + total_absence_dates = {} + total_half_dates = {} + total_recurring_dates = {} + all_absences = {} + delta = datetime.timedelta(days=1) + + for user in users: + # all the absences for the user + if user_type == 1: + user_username = user.user.username + else: + user_username = user.username + + absence_info = Absence.objects.filter(Target_User_ID__username=user_username) + total_absence_dates[user_username] = [] + total_recurring_dates[user_username] = [] + total_half_dates[user_username] = [] + all_absences[user_username] = [] + + # if they have any absences + if absence_info: + # mapping the absence content to keys in dictionary + for x in absence_info: + absence_id = x.ID + absence_date_start = x.absence_date_start + absence_date_end = x.absence_date_end + dates = absence_date_start + if x.half_day == "NORMAL": + while dates <= absence_date_end: + total_absence_dates[user_username].append(dates) + dates += delta + else: + total_half_dates[user_username].append( + { + "date": absence_date_start, + "type": x.half_day + } + ) + + absence_content.append( + { + "ID": absence_id, + "absence_date_start": absence_date_start, + "absence_date_end": absence_date_end, + "dates": total_absence_dates[user_username], + } + ) + + # for each user it maps the set of dates to a dictionary key labelled as the users name + all_absences[user_username] = absence_content + + recurring = RecurringAbsences.objects.filter(Target_User_ID__username=user_username) + + if recurring: + for recurrence_ in recurring: + dates = recurrence_.Recurrences.occurrences( + dtend=datetime.datetime.strptime( + str(datetime.datetime.now().year + 2), "%Y" + ) + ) + + for x in list(dates)[:-1]: + time_const = "23:00:00" + time_var = datetime.datetime.strftime(x, "%H:%M:%S") + if time_const == time_var: + x += timedelta(days=1) + + #print(RecurringException.objects.filter(Target_User_ID__username=user_username, Exception_Start=x).count()) + if RecurringException.objects.filter(Target_User_ID__username=user_username, Exception_Start=x).count() == 0: + total_recurring_dates[user_username].append(x) + # TODO: add auto deleting for recurring absences once last date of absences in before now + # if x < datetime.datetime.now(): + # pass + + data["recurring_absence_dates"] = total_recurring_dates + data["all_absences"] = all_absences + data["absence_dates"] = total_absence_dates + data["half_days_data"] = total_half_dates + data["users"] = users + return data + + +@login_required +def team_calendar( + request, + id, + month=datetime.datetime.now().strftime("%B"), + year=datetime.datetime.now().year, +): + if is_member(request.user, id): + + # Get acceptable date - (NOTHING BELOW now - 12months) + date = check_calendar_date(year, month) + if date: + month = date.strftime("%B") + year = date.year + + try: + userprofile: UserProfile = UserProfile.objects.filter(user=request.user)[0] + except IndexError: + return redirect("/") + + data_1 = get_date_data(userprofile.region, month, year) + + users = Relationship.objects.all().filter( + team=id, status=Status.objects.get(status="Active") + ) + + data_2 = get_absence_data(users, 1) + + team = Team.objects.get(id=id) + + # Filtering users by privacy + filtered_users = [] + viewer = data_2["users"].get(user=request.user) + if str(viewer.role) == "Follower": + # Than hide data of users with privacy on + for user in data_2["users"]: + user_profile = UserProfile.objects.get(user=user.user) + if not user_profile.privacy: + filtered_users.append(user) + + else: + # For pop-up to inform viewer that there are hidden users + data_2.update({"hiding_users": True}) + + data_2["users"] = filtered_users + + # Gets filtered users by filtering system on page + filtered_users = get_filter_users( + request, [user.user for user in data_2["users"]] + ) + + actual_filtered_users = [] + for user in data_2["users"]: + if user.user in filtered_users: + actual_filtered_users.append(user) + + # Reconstructs users list to be in desired form for template - (QuerySet of relationships) + data_2["users"] = actual_filtered_users + + user_in_teams = [] + for rel in Relationship.objects.filter(team=team): + user_in_teams.append(rel.user.id) + + data_3 = { + "owner": Role.objects.all()[0], + "Sa": "Sa", + "Su": "Su", + "current_user": Relationship.objects.get(user=request.user, team=team), + "team": team, + "all_users": User.objects.all().exclude(id__in=user_in_teams), + "team_count": Relationship.objects.filter( + team=team.id, status=Status.objects.get(status="Active") + ).count(), + } + + context = {**data_1, **data_2, **data_3} + + return render(request, "teams/calendar.html", context) + + return redirect("dashboard") + + +@login_required +def all_calendar( + request, + month=datetime.datetime.now().strftime("%B"), + year=datetime.datetime.now().year, +): + # Get acceptable date - (NOTHING BELOW now - 12months) + date = check_calendar_date(year, month) + if date: + month = date.strftime("%B") + year = date.year + + try: + userprofile: UserProfile = UserProfile.objects.filter(user=request.user)[0] + except IndexError: + return redirect("/") + + if userprofile.external_teams: + try: + if requests.get(env("TEAM_DATA_URL") + "api/teams/?username={}".format(request.user.username)).status_code == 200: + return redirect("/calendar/1") + except: + print("Failed to load api") + + + data_1 = get_date_data(userprofile.region, month, year) + + current_month = data_1["current_month"] + current_year = data_1["current_year"] + current_day = datetime.datetime.now().day + date = f"{current_day} {current_month} {current_year}" + date = datetime.datetime.strptime(date, "%d %B %Y") + + + all_users = [] + all_users.append(request.user) + + user_relations = Relationship.objects.filter( + user=request.user, status=Status.objects.get(status="Active") + ) + hiding_users = False + + + for index, relation in enumerate(user_relations): + rels = Relationship.objects.filter( + team=relation.team, status=Status.objects.get(status="Active") + ) + + # Finds the viewers role in the team + for user in rels: + if user.user == request.user: + viewers_role = Role.objects.get(id=user.role_id) + + for rel in rels: + if rel.user not in all_users: + if viewers_role.role == "Follower": + # Than hide users data who have privacy on + + user_profile = UserProfile.objects.get(user=rel.user) + if not user_profile.privacy: + # If user hasn't got their data privacy on + all_users.append(rel.user) + else: + # Used to inform user on calendar page if there are hiden users + hiding_users = True + + else: + # Only followers cannot view those who have privacy set for their data. - (Members & Owners can see the data) + all_users.append(rel.user) + + # Filtering + filtered_users = get_filter_users(request, all_users) + + data_2 = get_absence_data(all_users, 2) + + data_3 = {"Sa": "Sa", "Su": "Su", "users_filter": filtered_users} + + + grid_calendar_month_values = list(data_1["day_range"]) + # NOTE: This will select which value to use to fill in how many blank cells where previous month overrides prevailing months days. + # This is done by finding the weekday value for the 1st day of the month. Example: "Tu" will require 1 blank space/cell. + for i in range({"Mo":0, "Tu":1, "We":2, "Th":3, "Fr":4, "Sa":5, "Su":6}[data_1["days_name"][0]]): + grid_calendar_month_values.insert(0, -1) + + + context = { + **data_1, + **data_2, + **data_3, + "users_hidden": hiding_users, + "home_active": True, + + # Grid-Calendars day values + "detailed_calendar_day_range":grid_calendar_month_values, + + + } + + + return render(request, "ap_app/calendar.html", context) + +@login_required +def set_calendar_month(request): + if request.method == "POST": + month = request.POST.get('month_names') + + split = month.split() + + month = split[0] + + year = split[1] + + print(month) + + return redirect('all_calendar', month=month, year=year) + + +def text_rules(user): + recurring_absences = RecurringAbsences.objects.filter(Target_User_ID=user).values( + "Recurrences", "ID" + ) + rec_absences = {} + + for x in recurring_absences: + absence_ = x["Recurrences"] + if absence_: + rec_absences[x["ID"]] = [] + if absence_.exdates: + for y in absence_.exdates: + rec_absences[x["ID"]].append( + "Excluding Date: " + (y + timedelta(days=1)).strftime("%A,%d %B,%Y") + ) + if absence_.rdates: + for y in absence_.rdates: + rec_absences[x["ID"]].append( + "Date: " + (y + timedelta(days=1)).strftime("%A,%d %B,%Y") + ) + if absence_.rrules: + for y in absence_.rrules: + rec_absences[x["ID"]].append("Rule: " + str(y.to_text())) + + if absence_.exrules: + for y in absence_.exrules: + rec_absences[x["ID"]].append("Excluding Rule: " + str(y.to_text())) + + return rec_absences + + +# Profile page +@login_required +def profile_page(request): + if request.method == "POST": + form = SwitchUser( + request.POST, + initial={"user": request.user}, + ) + form.fields["user"].queryset = UserProfile.objects.filter( + edit_whitelist=request.user + ) + users = UserProfile.objects.filter(edit_whitelist=request.user) + rec_absences = text_rules(request.user) + + if form.is_valid(): + absence_user = form.cleaned_data["user"].user + + absences = Absence.objects.filter(Target_User_ID=absence_user) + rec_absences = text_rules(absence_user) + return render( + request, + "ap_app/profile.html", + { + "form": form, + "message": "Successfully switched user", + "absences": absences, + "users": users, + "recurring_absences": rec_absences, + }, + ) + else: + absences = Absence.objects.filter(Target_User_ID=request.user.id) + rec_absences = text_rules(request.user) + + users = UserProfile.objects.filter(edit_whitelist=request.user) + form = SwitchUser() + form.fields["user"].queryset = users + form.fields["user"].initial = request.user + + return render( + request, + "ap_app/profile.html", + { + "absences": absences, + "users": users, + "form": form, + "recurring_absences": rec_absences, + }, + ) + + +@login_required +def deleteuser(request): + """delete a user account""" + if request.method == "POST": + delete_form = DeleteUserForm(request.POST, instance=request.user) + user = request.user + user.delete() + messages.info(request, "Your account has been deleted.") + return redirect("index") + else: + delete_form = DeleteUserForm(instance=request.user) + + context = {"delete_form": delete_form} + + return render(request, "registration/delete_account.html", context) + + +@login_required +def absence_delete(request, absence_id: int, user_id: int, team_id: int = 1): + try: + absence = Absence.objects.get(pk=absence_id) + absence.delete() + except Absence.DoesNotExist: + pass + if request.user == User.objects.get(id = user_id): + return redirect("profile") + return redirect("edit_team_member_absence", team_id, user_id) + + +@login_required +def absence_recurring_delete( + request, absence_id: int, user_id: int, team_id: int = None +): + absence = RecurringAbsences.objects.get(pk=absence_id) + user = request.user + absence.delete() + if user == absence.Target_User_ID: + return redirect("profile") + return redirect("edit_team_member_absence", team_id, user_id) + + +class EditAbsence(UpdateView): + template_name = "ap_app/edit_absence.html" + model = Absence + + # specify the fields + fields = ["absence_date_start", "absence_date_end"] + + def get_success_url(self) -> str: + return reverse("profile") + + +@login_required +def edit_recurring_absences(request, pk): + absence = RecurringAbsences.objects.get(ID=pk) + + if request.method == "POST": + form = RecurringAbsencesForm(request.POST, instance=absence) + form2 = TargetUserForm(request.POST, target_user=request.user) + form2.fields["target_user"].queryset = UserProfile.objects.filter( + edit_whitelist__in=[request.user] + ) + rule = str(form["Recurrences"].value()) + + if not ("DAILY" in rule or "BY" in rule): + content = { + "form": form, + "form2": form2, + "message": "Must select a day/month", + } + return render(request, "ap_app/edit_recurring_absence.html", content) + + if form2.is_valid(): + absence.Target_User_ID = form2.cleaned_data["target_user"].user + absence.recurrences = form["Recurrences"].value() + absence.save() + return redirect("index") + else: + form = RecurringAbsencesForm(instance=absence) + + form2 = TargetUserForm(target_user=absence.Target_User_ID) + form2.fields["target_user"].queryset = UserProfile.objects.filter( + edit_whitelist__in=[absence.User_ID] + ) + + return render( + request, "ap_app/edit_recurring_absence.html", {"form": form, "form2": form2} + ) + + +@login_required +def profile_settings(request) -> render: + """returns the settings page""" + + if len(request.POST) > 0: + if request.POST.get("firstName") != "" and request.POST.get("firstName") != request.user.first_name: + request.user.first_name = request.POST.get("firstName") + request.user.save() + if request.POST.get("lastName") != "" and request.POST.get("lastName") != request.user.last_name: + request.user.last_name = request.POST.get("lastName") + request.user.save() + if request.POST.get("email") != request.user.email: + request.user.email = request.POST.get("email") + request.user.save() + + try: + userprofile: UserProfile = UserProfile.objects.filter(user=request.user)[0] + except IndexError: + # TODO Create error page + return redirect("/") + + if len(request.POST) > 0: + region = request.POST.get("region") + region_code = pycountry.countries.get(name=region).alpha_2 + if region_code != userprofile.region: + userprofile.region = region_code + if request.POST.get("privacy") == None: + userprofile.privacy = False + elif request.POST.get("privacy") == "on": + userprofile.privacy = True + if request.POST.get("teams") == None: + userprofile.external_teams = False + elif request.POST.get("teams") == "on": + userprofile.external_teams = True + userprofile.save() + + country_data = get_region_data() + country_name = pycountry.countries.get(alpha_2=userprofile.region).name + + privacy_status = userprofile.privacy + teams_status = userprofile.external_teams + context = {"userprofile": userprofile, "data_privacy_mode": privacy_status, "external_teams": teams_status,"current_country": country_name, **country_data} + return render(request, "ap_app/settings.html", context) + + +@login_required +def add_user(request) -> render: + data=json.loads(request.body) + try: + userprofile: UserProfile = UserProfile.objects.filter(user=request.user)[0] + except IndexError: + # TODO Create error page + return redirect("/") + + if request.method == "POST": + username = data["username"] + + try: + user = User.objects.get(username=username) + except: + # TODO Create error page + return redirect("/") + + userprofile.edit_whitelist.add(user) + user.save() + + return redirect("/profile/settings") + +def get_region_data(): + data = {} + data["countries"] = [] + for country in list(pycountry.countries): + try: + holidays.country_holidays(country.alpha_2) + data["countries"].append(country.name) + except: + pass + + data["countries"] = sorted(data["countries"]) + + return data + +@login_required +def set_region(request): + + try: + userporfile: UserProfile = UserProfile.objects.filter(user=request.user)[0] + except IndexError: + # TODO Create error page + return redirect("/") + + if request.method == "POST": + region = request.POST.get("regions") + region_code = pycountry.countries.get(name=region).alpha_2 + if (region_code != userporfile.region): + userporfile.region = region_code + userporfile.save() + + return redirect("/profile/settings") + +def find_user_obj(user_to_find): + """Finds & Returns object of 'UserProfile' for a user + \n-param (type)User user_to_find + """ + users = UserProfile.objects.filter(user=user_to_find) + # If cannot find object for a user, than creates on + + if users.count() <= 0: + UserProfile.objects.create(user=user_to_find, accepted_policy=False) + user_found = UserProfile.objects.filter(user=user_to_find)[0] + user_found.edit_whitelist.add(user_to_find) + + # Users object + user_found = UserProfile.objects.filter(user=user_to_find)[0] + + return user_found + + +def obj_exists(user): + """Determines if a user has a 'UserProfile' Object""" + objs = UserProfile.objects.filter(user=user) + if objs.count() == 0: + return False + + return True + + +def get_filter_users(request, users) -> list: + """Used for calendar filtering system - (Returns list of filtered users depending on + search-bar and 'filter by absence' checkbox""" + + filtered_users = [] + + # Filtering by both username & absence + if "username" in request.GET and "absent" in request.GET: + # Get username input & limits the length to 50 + name_filtered_by = request.GET["username"][:50] + for absence in Absence.objects.all(): + user = User.objects.get(id=absence.Target_User_ID.id) + if user in users: + username = user.username + if ( + absence.Target_User_ID not in filtered_users + and name_filtered_by.lower() in username.lower() + ): + filtered_users.append(absence.Target_User_ID) + + # ONLY filtering by username + elif "username" in request.GET: + # Name limit is 50 + name_filtered_by = request.GET["username"][:50] + for user in users: + # same logic as "icontains", searches through users names & finds similarities + if name_filtered_by.lower() in user.username.lower(): + filtered_users.append(user) + + # ONLY filtering by absences + elif "absent" in request.GET: + for absence in Absence.objects.all(): + user = User.objects.get(id=absence.Target_User_ID.id) + if user in users and absence.Target_User_ID not in filtered_users: + filtered_users.append(absence.Target_User_ID) + + # Else, no filtering + else: + filtered_users = users + + return filtered_users + + +def is_member(user, team_id) -> bool: + """ Determines if the user is a member of the team before accessing its contents """ + + team = Relationship.objects.filter(team=Team.objects.get(id=team_id)) + + # Boolean determines if viewer is in this team + for rel in team: + if rel.user == user: + return True + + # Else user has changed URL & is attempting to view other teams content + return False + + +def is_owner(user, team_id) -> bool: + """ Determines if the user is an owner of the team """ + + user_relation = Relationship.objects.get(team=team_id, user=user) + return (user_relation.role.role == "Owner") + + +def check_calendar_date(year, month) -> datetime.datetime: + """ This function will determine if the requested date is acceptable - (NOT before current date - 12 months) """ + # Current year * 12 + current month as num = amount of months been + # requested year * 12 + requested month as num = + + # If requested date is before "current date - 12 months" than will not accept date + todays_date = datetime.datetime.now() + requested_months_amount = (int(year) * 12) + datetime.datetime.strptime(month, "%B").month + current_months_amount = (todays_date.year * 12) + todays_date.month + months_difference = current_months_amount - requested_months_amount + + + if months_difference > 12: + # Return the most earliest date acceptable - (now - 12 months) + return datetime.datetime(todays_date.year - 1, todays_date.month, 1) + else: + return None + +from django.shortcuts import render + +def my_view(request): + return render(request, "base.html") + + +def handler404(request, exception): + context = {} + response = render(request, "404.html", context=context) + response.status_code = 404 + return response + +def handler500(request): + context = {} + response = render(request, "500.html", context=context) + response.status_code = 500 + return response + +def handler400(request, exception): + context = {} + response = render(request, "400.html", context=context) + response.status_code = 400 + return response + + +#JC - Calendar view using the API +@login_required +def api_calendar_view( + request, + #JC - These are the default values for the calendar. + month=datetime.datetime.now().strftime("%B"), + year=datetime.datetime.now().year +): + + try: + userprofile: UserProfile = UserProfile.objects.get(user=request.user) + except IndexError: + return redirect("/") + + if not userprofile.external_teams: + return redirect("/calendar/0") + + #JC - Get API data + api_data = None + if request.method == "GET": + try: + r = requests.get(env("TEAM_DATA_URL") + "api/teams/?username={}".format(request.user.username)) + except: + print("API failed to connect") + return redirect("/") + if r.status_code == 200: + api_data = r.json() + else: + if r: + result = r.json() + if result["code"] == "I": + print("Username not found in Team App database.") + elif result["code"] == "N": + print("A username was not provided with the request.") + else: + print("Fatal Error") + + date = check_calendar_date(year, month) + if date: + month = date.strftime("%B") + year = date.year + + data_1 = get_date_data(userprofile.region, month, year) + + current_month = data_1["current_month"] + current_year = data_1["current_year"] + current_day = datetime.datetime.now().day + date = f"{current_day} {current_month} {current_year}" + date = datetime.datetime.strptime(date, "%d %B %Y") + + + all_users = [] + all_users.append(request.user) + + if api_data: + for team in api_data: + for member in team["team"]["members"]: + retrieved_user = User.objects.filter(username=member["user"]["username"]) + if retrieved_user.exists() and retrieved_user not in all_users: + all_users.append(retrieved_user[0]) + + data_2 = get_absence_data(all_users, 2) + + data_3 = {"Sa": "Sa", "Su": "Su"} + + grid_calendar_month_values = list(data_1["day_range"]) + # NOTE: This will select which value to use to fill in how many blank cells where previous month overrides prevailing months days. + # This is done by finding the weekday value for the 1st day of the month. Example: "Tu" will require 1 blank space/cell. + for i in range({"Mo":0, "Tu":1, "We":2, "Th":3, "Fr":4, "Sa":5, "Su":6}[data_1["days_name"][0]]): + grid_calendar_month_values.insert(0, -1) + + + context = { + **data_1, + **data_2, + **data_3, + "home_active": True, + "api_data": api_data, + } + + return render(request, "api_pages/calendar.html", context) diff --git a/ap_src/ap_site/example_env.txt b/ap_src/ap_site/example_env.txt deleted file mode 100644 index b0bdda2e..00000000 --- a/ap_src/ap_site/example_env.txt +++ /dev/null @@ -1 +0,0 @@ -TEAM_DATA_URL=http://localhost:8000/ \ No newline at end of file diff --git a/ap_src/ap_site/settings.py b/ap_src/ap_site/settings.py index 46273b25..82a7726e 100644 --- a/ap_src/ap_site/settings.py +++ b/ap_src/ap_site/settings.py @@ -13,9 +13,10 @@ import os import environ from pathlib import Path -from dotenv import load_dotenv -load_dotenv() +# Retrieve Environment Variables +env = environ.Env() +environ.Env.read_env() # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -28,11 +29,7 @@ SECRET_KEY = "django-insecure-duam^e#bui)v&(*6!z5j1_9!mm55v#o(b_ni77ttxm#55bzs=1" # SECURITY WARNING: don't run with debug turned on in production! -DEBUG_ENV = os.getenv("DEBUG") -DEBUG = DEBUG_ENV is not None and DEBUG_ENV.lower() == "true" - -#Location of the CSS files -STATIC_ROOT = BASE_DIR / 'static/css' +DEBUG = True ALLOWED_HOSTS = ["*"] @@ -105,17 +102,6 @@ } } -if not DEBUG: - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': os.environ["DB_NAME"], - 'USER': os.environ["DB_USER"], - "PASSWORD": os.environ["DB_PASSWORD"], - "HOST": os.environ["DB_HOST"], - } - } - # Password validation # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators @@ -155,9 +141,9 @@ # https://docs.djangoproject.com/en/3.2/howto/static-files/ STATIC_URL = "/static/" -STATIC_ROOT = os.path.join(BASE_DIR) + "/static" +STATIC_ROOT = str(BASE_DIR) + "/static_root" -#STATICFILES_DIRS = [str(BASE_DIR) + "/ap_app/static"] +STATICFILES_DIRS = [str(BASE_DIR) + "/ap_app/static"] # Default primary key field type @@ -173,6 +159,5 @@ SHORT_DATE_FORMAT = "dd/mm/YYYY" # Info retrieved from environment variables -PRODUCTION_UI_ENV = os.getenv("PRODUCTION_UI") -PRODUCTION_UI = PRODUCTION_UI_ENV is not None and PRODUCTION_UI_ENV.lower() == "true" +PRODUCTION_UI = False VERSION = "1.3.0" \ No newline at end of file diff --git a/ap_src/ap_site/urls.py b/ap_src/ap_site/urls.py index edcdc8ef..2dc3753f 100644 --- a/ap_src/ap_site/urls.py +++ b/ap_src/ap_site/urls.py @@ -24,4 +24,4 @@ path("accounts/login", auth_views.LoginView.as_view(redirect_authenticated_user=True), name="login"), path("", include("ap_app.urls")), -] +] diff --git a/ap_src/templates/ap_app/calendar.html b/ap_src/templates/ap_app/calendar.html index 0459672b..44d6a16c 100644 --- a/ap_src/templates/ap_app/calendar.html +++ b/ap_src/templates/ap_app/calendar.html @@ -6,31 +6,49 @@ {% block title %}Calendar{% endblock %} {% load static %} {% block content %} + -
-
@@ -172,7 +187,7 @@

- {% elif current_month_num == month_num %} + {% elif month_num == 12 %}

- {% else %} @@ -252,7 +266,7 @@

{% endif %} {% endif %} -
+ {% csrf_token %}
-
- +
- - -
- Toggle Clickable Calendar

-

-
- {% comment %} +

-

-
{% endcomment %} - - - - - - Tool Box - + + +

- @@ -355,12 +371,12 @@

-

Use Left Click to select a full day

Use Shift Left Click to select a half-day

If half-day booked, top half of the box means Afternoon has been marked as absence, bottom half means Morning has been marked as absence

- Prototype Grid-Based Calendar --> - {% comment %}
+ +
@@ -406,7 +422,7 @@

{% endfor %}

- {% endcomment %} + @@ -417,8 +433,7 @@

{% for day in days_name %} {% if day == Sa or day == Su %} - {%comment%}{{day}}{%endcomment%} - + {{day}} {% elif forloop.counter in bank_hol%} {{day}} {% else %} @@ -432,10 +447,9 @@

{% if day == today and month == current_month and year == current_year %} {{day}} {% elif day in bank_hol %} - {{day}} + {{day}} {% elif day in weekend_list%} - {%comment%}{{day}}{%endcomment%} - + {{day}} {% else %} {{day}} {% endif %} @@ -447,8 +461,7 @@

{% if user.id == users.0.id %} {{user.username}} {% else %} - {% check_permissions user request.user as editable %} - {% if editable %} + {% if user|check_permissions:users.0 %} {{user.username}}* {% else %} {{user.username}} @@ -472,8 +485,7 @@

{% if day in bank_hol %} {% elif day in weekend_list %} - {%comment%}{%endcomment%} - + {% else %} {% endif %} @@ -488,26 +500,9 @@

- - - - - - - diff --git a/ap_src/templates/ap_app/elements/colour_picker.html b/ap_src/templates/ap_app/elements/colour_picker.html deleted file mode 100644 index 0ffd3245..00000000 --- a/ap_src/templates/ap_app/elements/colour_picker.html +++ /dev/null @@ -1,39 +0,0 @@ -{% load static %} -
- {% csrf_token %} - - -
\ No newline at end of file diff --git a/ap_src/templates/ap_app/elements/confirmation_modal.html b/ap_src/templates/ap_app/elements/confirmation_modal.html deleted file mode 100644 index c1eee71c..00000000 --- a/ap_src/templates/ap_app/elements/confirmation_modal.html +++ /dev/null @@ -1,49 +0,0 @@ -{% load static %} -