Skip to content
Snippets Groups Projects
Commit 268325b2 authored by Ole-Christian Bjerkeset's avatar Ole-Christian Bjerkeset
Browse files

modified the user app in the backend to support fitness profiles

added functionality for fitness profiles in frontend
parent ebc2ecb7
No related branches found
No related tags found
1 merge request!2Merge 34-fitnessProfile into master
Pipeline #160905 passed
/env
\ No newline at end of file
......@@ -18,6 +18,10 @@ class User(AbstractUser):
country = models.TextField(max_length=50, blank=True)
city = models.TextField(max_length=50, blank=True)
street_address = models.TextField(max_length=50, blank=True)
age = models.PositiveIntegerField(blank=True, null=True)
expirience = models.PositiveIntegerField(blank=True, null=True)
favorite_dicipline = models.TextField(max_length=50, blank=True, null=True)
bio = models.TextField(max_length=200, blank=True, null=True)
def athlete_directory_path(instance, filename):
......
......@@ -73,21 +73,47 @@ class UserGetSerializer(serializers.HyperlinkedModelSerializer):
"workouts",
"coach_files",
"athlete_files",
"age",
"expirience",
"favorite_dicipline",
"bio"
]
class UserPutSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ["athletes"]
fields = [
"athletes",
"age",
"expirience",
"favorite_dicipline",
"bio"
]
def update(self, instance, validated_data):
athletes_data = validated_data["athletes"]
age_data = validated_data["age"]
expirience_data = validated_data["expirience"]
favorite_dicipline_data = validated_data["favorite_dicipline"]
bio_data = validated_data["bio"]
instance.athletes.set(athletes_data)
instance.age.set(age_data)
instance.expirience.set(expirience_data)
instance.favorite_dicipline.set(favorite_dicipline_data)
instance.bio.set(bio_data)
return instance
class ProfilePutSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = [
"age",
"expirience",
"favorite_dicipline",
"bio"
]
class AthleteFileSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source="owner.username")
......
......@@ -5,6 +5,7 @@ from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [
path("api/users/", views.UserList.as_view(), name="user-list"),
path("api/users/<int:pk>/", views.UserDetail.as_view(), name="user-detail"),
path("api/profiles/<int:pk>/", views.ProfileUpdate.as_view(), name="profile-update"),
path("api/users/<str:username>/", views.UserDetail.as_view(), name="user-detail"),
path("api/offers/", views.OfferList.as_view(), name="offer-list"),
path("api/offers/<int:pk>/", views.OfferDetail.as_view(), name="offer-detail"),
......
......@@ -8,6 +8,7 @@ from users.serializers import (
AthleteFileSerializer,
UserPutSerializer,
UserGetSerializer,
ProfilePutSerializer
)
from rest_framework.permissions import (
AllowAny,
......@@ -81,6 +82,17 @@ class UserDetail(
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
class ProfileUpdate(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
generics.GenericAPIView,
):
serializer_class = ProfilePutSerializer
queryset = get_user_model().objects.all()
permission_classes = [permissions.IsAuthenticated & IsCurrentUser]
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
class OfferList(
mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
......@@ -195,4 +207,4 @@ class AthleteFileDetail(
return self.retrieve(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
return self.destroy(request, *args, **kwargs)
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Group</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<script src="https://kit.fontawesome.com/0ce6c392ca.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="styles/style.css">
<script src="scripts/navbar.js" type="text/javascript" defer></script>
</head>
<body>
<navbar-el></navbar-el>
<div class="container">
<div class="row">
<div class="col-lg">
<h3 class="mt-3" id="title"></h3>
<p>Edit you fitness information by clicking the edit button below!</p>
</div>
</div>
<form class="row g-3" id="form-profile">
<div class="col-lg-6 ">
<label for="inputAge" class="form-label">Age</label>
<input type="number" class="form-control" id="inputAge" name="age" readonly>
</div>
<div class="col-lg-6"></div>
<div class="col-lg-6">
<label for="inputExpirience" class="form-label">Workout experience(years)</label>
<input type="number" class="form-control" id="inputExpirience" name="expirience" readonly>
</div>
<div class="col-lg-6"></div>
<div class="col-lg-6">
<label for="inputDicipline" class="form-label">Favorite diciplines</label>
<textarea class="form-control" id="inputDicipline" name="favorite_dicipline" readonly></textarea>
</div>
<div class="col-lg-6"></div>
<div class="col-lg-6">
<label for="inputBio" class="form-label">About me</label>
<textarea class="form-control" id="inputBio" name="bio" readonly></textarea>
</div>
<div class="col-lg-6"></div>
<div class="col-lg-6">
<input type="button" class="btn btn-primary hide" id="btn-ok-profile" value=" OK ">
<input type="button" class="btn btn-secondary hide" id="btn-cancel-profile" value="Cancel">
<input type="button" class="btn btn-primary" id="btn-edit-profile" value=" Edit ">
</div>
<div class="col-lg-6"></div>
</form>
</div>
<script src="scripts/defaults.js"></script>
<script src="scripts/scripts.js"></script>
<script src="scripts/profile.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
</body>
</html>
\ No newline at end of file
......@@ -20,6 +20,7 @@ class NavBar extends HTMLElement {
<a class="nav-link hide" id="nav-myathletes" href="myathletes.html">Athletes</a>
<a class="nav-link hide" id="nav-meals" href="meals.html">Meal registration</a>
<a class="nav-link hide" id="nav-groups" href="groups.html">Groups</a>
<a class="nav-link hide" id="nav-profile" href="profile.html">Fitness Profile</a>
<hr>
</div>
<div class="my-2 my-lg-0 me-5">
......
let cancelButton;
let okButton;
let editButton;
let oldFormData;
function handleCancelButtonDuringCreate() {
window.location.replace("profile.html");
}
/**
* If user presses "cancel" during editing a group the
* form fields become read only and the form data is deleted.
* And the buttons change.
*/
function handleCancelButtonDuringEdit() {
setReadOnly(true, "#form-profile");
okButton.className += " hide";
cancelButton.className += " hide";
editButton.className = editButton.className.replace(" hide", "");
cancelButton.removeEventListener("click", handleCancelButtonDuringEdit);
let form = document.querySelector("#form-profile");
if (oldFormData.has("age")) form.name.value = oldFormData.get("age");
if (oldFormData.has("expirience")) form.description.value = oldFormData.get("expirience");
if (oldFormData.has("dicipline")) form.description.value = oldFormData.get("dicipline");
if (oldFormData.has("bio")) form.description.value = oldFormData.get("bio");
oldFormData.delete("age");
oldFormData.delete("expirience");
oldFormData.delete("dicipline");
oldFormData.delete("bio");
}
/**
* If the user clicks on the edit button the form fields can be edited.
* And the form is updated with the data of the group that the user wants to edit.
*/
function handleEditProfileButtonClick() {
setReadOnly(false, "#form-profile");
editButton.className += " hide";
okButton.className = okButton.className.replace(" hide", "");
cancelButton.className = cancelButton.className.replace(" hide", "");
cancelButton.addEventListener("click", handleCancelButtonDuringEdit);
let form = document.querySelector("#form-profile");
oldFormData = new FormData(form);
}
/**
* sends an API request to retrieve the profile information of the user that is currently active
*/
async function getCurrentProfile() {
let res = await sendRequest("GET", `${HOST}/api/users/?user=current`);
if (!res.ok) {
console.log("COULD NOT RETRIEVE CURRENTLY LOGGED IN USER");
} else {
let data = await res.json();
userID = data.results[0].id;
userName = data.results[0].username
}
document.getElementById("title").innerHTML = userName + "'s Fitness Profile";
let response = await sendRequest("GET", `${HOST}/api/users/${userID}/`);
if (!response.ok) {
console.log("COULD NOT RETRIEVE CURRENTLY LOGGED IN USER");
} else {
let profileData = await response.json();
let form = document.querySelector("#form-profile");
let formData = new FormData(form);
for (let key of formData.keys()) {
let selector
selector = `input[name="${key}"], textarea[name="${key}"]`;
let input = form.querySelector(selector);
let newVal = profileData[key];
input.value = newVal;
}
}
}
/**
* Sends a PUT request to the API to update a group's information.
* @param {integer} id of the group to be updated
*/
async function updateProfile() {
let res = await sendRequest("GET", `${HOST}/api/users/?user=current`);
if (!res.ok) {
console.log("COULD NOT RETRIEVE CURRENTLY LOGGED IN USER");
} else {
let data = await res.json();
userID = data.results[0].id;
}
let form = document.querySelector("#form-profile");
let formData = new FormData(form);
let body = {"age": formData.get("age"),
"expirience": formData.get("expirience"),
"favorite_dicipline": formData.get("favorite_dicipline"),
"bio": formData.get("bio"),
};
let response = await sendRequest("PUT", `${HOST}/api/profiles/${userID}/`, body);
if (!response.ok) {
let data = await response.json();
let alert = createAlert(`Could not update profile`, data);
document.body.prepend(alert);
} else {
setReadOnly(true, "#form-profile");
okButton.className += " hide";
cancelButton.className += " hide";
editButton.className = editButton.className.replace(" hide", "");
cancelButton.removeEventListener("click", handleCancelButtonDuringEdit);
oldFormData.delete("age");
oldFormData.delete("expirience");
oldFormData.delete("dicipline");
oldFormData.delete("bio");
}
}
/**
* When a user enters the group.html this decides whether it
* is entered in view/edit mode or in create mode. If the html contains
* a url parameter with an id it is entered in view/edit mode.
*/
window.addEventListener("DOMContentLoaded", async () => {
cancelButton = document.querySelector("#btn-cancel-profile");
okButton = document.querySelector("#btn-ok-profile");
editButton = document.querySelector("#btn-edit-profile");
oldFormData = null;
await getCurrentProfile();
editButton.addEventListener("click", handleEditProfileButtonClick);
okButton.addEventListener("click", async () => await updateProfile());
});
\ No newline at end of file
......@@ -27,6 +27,9 @@ function updateNavBar() {
} else if (window.location.pathname == "/groups.html") {
makeNavLinkActive("nav-groups");
}
else if (window.location.pathname == "/profile.html") {
makeNavLinkActive("nav-profile");
}
if (isUserAuthenticated()) {
document.getElementById("btn-logout").classList.remove("hide");
......@@ -38,6 +41,7 @@ function updateNavBar() {
document.querySelector('a[href="myathletes.html"').classList.remove("hide");
document.querySelector('a[href="meals.html"').classList.remove("hide");
document.querySelector('a[href="groups.html"').classList.remove("hide");
document.querySelector('a[href="profile.html"').classList.remove("hide");
} else {
document.getElementById("btn-login-nav").classList.remove("hide");
document.getElementById("btn-register").classList.remove("hide");
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment