Skip to content
Snippets Groups Projects
Commit 141ef533 authored by asmundh's avatar asmundh
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Showing
with 548 additions and 0 deletions
.env 0 → 100644
GROUPID=90
DOMAIN=localhost
URL_PREFIX=http://
PORT_PREFIX=90
DJANGO_SUPERUSER_PASSWORD=Password
DJANGO_SUPERUSER_USERNAME=admin
DJANGO_SUPERUSER_EMAIL=admin@mail.com
backend/secfit/.vscode/
backend/secfit/*/migrations/__pycache__/
backend/secfit/*/__pycache__/
backend/secfit/db.sqlite3
# Webserver running nginx
FROM nginx:perl
# Import groupid environment variable
ENV GROUPID=${GROUPID}
ENV PORT_PREFIX=${PORT_PREFIX}
# Copy nginx config to the container
COPY nginx.conf /etc/nginx/nginx.conf
\ No newline at end of file
README.md 0 → 100644
# SecFit
SecFit (Secure Fitness) is a hybrid mobile application for fitness logging.
## Deploy with Docker
### Prerequisites:
Docker
Git
Windows hosts must use Education or more advanced versions to run Docker \
Download: https://innsida.ntnu.no/wiki/-/wiki/English/Microsoft+Windows+10
### Install:
$ git clone https://gitlab.stud.idi.ntnu.no/kyleo/secfit.git \
$ cd secfit/
### Run:
$ docker-compose up --build \
Hosts the application on http://localhost:9090 with default settings
## Technology
- **deployment** Docker
- **web** Nginx
- **database** Postgre SQL
- **backend** Django 3 with Django REST framework
- **application**
- **browser** - HTML5/CSS/JS, Bootstrap v5 (no jQuery dependency)
- **mobile** Apache Cordova (uses same website)
- **authentication** JWT
## Code and structure
.gitlab-ci.yml - gitlab ci
requirements.txt - Python requirements
package.json - Some node.js requirements, this is needed for cordova
- **secfit/** django project folder containing the project modules
- **<application_name>/** - generic structure of a django application
- **admins.py** - file contaning definitions to connect models to the django admin panel
- **urls.py** - contains mapping between urls and views
- **models.py** - contains data models
- **permissions.py** - contains custom permissions that govern access
- **serializers.py** - contains serializer definitions for sending data between backend and frontend
- **parsers.py** - contains custom parsers for parsing the body of HTTP requests
- **tests/** - contains tests for the module. [View Testing in Django](https://docs.djangoproject.com/en/2.1/topics/testing/) for more.
- **views.py** - Controller in MVC. Methods for rendering and accepting user data
- **forms.py** - definitions of forms. Used to render html forms and verify user input
- **settings.py** - Contains important settings at the application and/or project level
- **Procfile** - Procfile for backend heroku deployment
- **media/** - directory for file uploads (need to commit it for heroku)
- **comments/** - application handling user comments and reactions
- **secfit/** - The projects main module containing project-level settings.
- **users/** - application handling users and requests
- **workouts/** - application handling exercises and workouts
- **manage.py** - entry point for running the project.
- **seed.json** - contains seed data for the project to get it up and running quickly (coming soon)
## Local setup
It's recommended to have a look at: https://www.djangoproject.com/start/
Just as important is the Django REST guide: https://www.django-rest-framework.org/
Create a virtualenv https://docs.python-guide.org/dev/virtualenvs/
### Django
Installation with examples for Ubuntu. Windows and OSX is mostly the same
Fork the project and clone it to your machine.
#### Setup and activation of virtualenv (env that prevents python packages from being installed globaly on the machine)
Naviagate into the project folder, and create your own virtual environment
#### Install python requirements
`pip install -r requirements.txt`
#### Migrate database
`python manage.py migrate`
#### Create superuser
Create a local admin user by entering the following command:
`python manage.py createsuperuser`
Only username and password is required
#### Start the app
`python manage.py runserver`
#### Add initial data
You can add initial data either by going to the url the app is running on locally and adding `/admin` to the url.
Add some categories and you should be all set.
Or by entering
`python manage.py loaddata seed.json`
### Cordova
Cordova CLI guide: https://cordova.apache.org/docs/en/latest/guide/cli/
If you want to run this as a mobile application
- Navigate to the frontend directory
- For android, do `cordova run android`
- For ios, do `cordova run ios`
- For browser, do `cordova run browser`
It's possible you will need to add the platforms you want to run and build.
The following documentation can be used to run the application in an Android emulator: \
https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html
# Use the official Python image from the Docker Hub
FROM python:3.8.5-slim
# These two environment variables prevent __pycache__/ files.
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
# Make a new directory to put our code in.
RUN mkdir /code
# Change the working directory.
# Every command after this will be run from the /code directory.
WORKDIR /code
# Copy the of the code.
COPY . /code/
# Add sqlite3 to enable dbshell command for managing the database
RUN apt-get update -y && apt-get install sqlite3 -y
# Upgrade pip
RUN pip install --upgrade pip
# Install the requirements.
RUN pip install -r requirements.txt
# Import groupid environment variable
ENV GROUPID=${GROUPID}
# Initialize Django
RUN python manage.py migrate
# Import credential variables
ARG DJANGO_SUPERUSER_USERNAME
ARG DJANGO_SUPERUSER_PASSWORD
ARG DJANGO_SUPERUSER_EMAIL
# Create superuser
RUN DJANGO_SUPERUSER_USERNAME=${DJANGO_SUPERUSER_USERNAME} \
DJANGO_SUPERUSER_PASSWORD=${DJANGO_SUPERUSER_PASSWORD} \
DJANGO_SUPERUSER_EMAIL=${DJANGO_SUPERUSER_EMAIL} \
python manage.py createsuperuser --noinput || \
echo "WARNING: This error is ignored as it most likely is 'That username is already taken.'" \
&& echo "If you wish to alter the user credentials, then delete the user first."
# Create some exercises from seed data
RUN python manage.py loaddata seed.json
# Run wsgi server with gunicorn
CMD ["gunicorn", "secfit.wsgi", "--log-file", "-", "-b", "0.0.0.0:8000"]
from django.contrib import admin
# Register your models here.
from .models import Comment
admin.site.register(Comment)
from django.apps import AppConfig
class CommentsConfig(AppConfig):
name = "comments"
# Generated by Django 3.1 on 2020-08-21 03:42
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
("workouts", "__first__"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="Comment",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("content", models.TextField()),
("timestamp", models.DateTimeField(auto_now_add=True)),
(
"owner",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="comments",
to=settings.AUTH_USER_MODEL,
),
),
(
"workout",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="comments",
to="workouts.workout",
),
),
],
options={
"ordering": ["-timestamp"],
},
),
migrations.CreateModel(
name="Like",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("timestamp", models.DateTimeField(auto_now_add=True)),
(
"comment",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="likes",
to="comments.comment",
),
),
(
"owner",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="likes",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
from django.db import models
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
from django.db import models
from django.contrib.auth import get_user_model
from workouts.models import Workout
# Create your models here.
class Comment(models.Model):
"""Django model for a comment left on a workout.
Attributes:
owner: Who posted the comment
workout: The workout this comment was left on.
content: The content of the comment.
timestamp: When the comment was created.
"""
owner = models.ForeignKey(
get_user_model(), on_delete=models.CASCADE, related_name="comments"
)
workout = models.ForeignKey(
Workout, on_delete=models.CASCADE, related_name="comments"
)
content = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-timestamp"]
class Like(models.Model):
"""Django model for a reaction to a comment.
Attributes:
owner: Who liked the comment
comment: The comment that was liked
timestamp: When the like occurred.
"""
owner = models.ForeignKey(
get_user_model(), on_delete=models.CASCADE, related_name="likes"
)
comment = models.ForeignKey(Comment, on_delete=models.CASCADE, related_name="likes")
timestamp = models.DateTimeField(auto_now_add=True)
from rest_framework import permissions
class IsCommentVisibleToUser(permissions.BasePermission):
"""
Custom permission to only allow a comment to be viewed
if one of the following holds:
- The comment is on a public visibility workout
- The comment was written by the user
- The comment is on a coach visibility workout and the user is the workout owner's coach
- The comment is on a workout owned by the user
"""
def has_object_permission(self, request, view, obj):
# Write permissions are only allowed to the owner.
return (
obj.workout.visibility == "PU"
or obj.owner == request.user
or (obj.workout.visibility == "CO" and obj.owner.coach == request.user)
or obj.workout.owner == request.user
)
from rest_framework import serializers
from rest_framework.serializers import HyperlinkedRelatedField
from comments.models import Comment, Like
from workouts.models import Workout
class CommentSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source="owner.username")
workout = HyperlinkedRelatedField(
queryset=Workout.objects.all(), view_name="workout-detail"
)
class Meta:
model = Comment
fields = ["url", "id", "owner", "workout", "content", "timestamp"]
class LikeSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source="owner.username")
comment = HyperlinkedRelatedField(
queryset=Comment.objects.all(), view_name="comment-detail"
)
class Meta:
model = Like
fields = ["url", "id", "owner", "comment", "timestamp"]
from django.test import TestCase
# Create your tests here.
from django.urls import path, include
from comments.models import Comment, Like
from comments.views import CommentList, CommentDetail, LikeList, LikeDetail
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [
path("api/comments/", CommentList.as_view(), name="comment-list"),
path("api/comments/<int:pk>/", CommentDetail.as_view(), name="comment-detail"),
path("api/likes/", LikeList.as_view(), name="like-list"),
path("api/likes/<int:pk>/", LikeDetail.as_view(), name="like-detail"),
]
from django.shortcuts import render
from rest_framework import generics, mixins
from comments.models import Comment, Like
from rest_framework import permissions
from comments.permissions import IsCommentVisibleToUser
from workouts.permissions import IsOwner, IsReadOnly
from comments.serializers import CommentSerializer, LikeSerializer
from django.db.models import Q
from rest_framework.filters import OrderingFilter
# Create your views here.
class CommentList(
mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
):
# queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [OrderingFilter]
ordering_fields = ["timestamp"]
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def get_queryset(self):
workout_pk = self.kwargs.get("pk")
qs = Comment.objects.none()
if workout_pk:
qs = Comment.objects.filter(workout=workout_pk)
elif self.request.user:
"""A comment should be visible to the requesting user if any of the following hold:
- The comment is on a public visibility workout
- The comment was written by the user
- The comment is on a coach visibility workout and the user is the workout owner's coach
- The comment is on a workout owned by the user
"""
# The code below is kind of duplicate of the one in ./permissions.py
# We should replace it with a better solution.
# Or maybe not.
qs = Comment.objects.filter(
Q(workout__visibility="PU")
| Q(owner=self.request.user)
| (
Q(workout__visibility="CO")
& Q(workout__owner__coach=self.request.user)
)
| Q(workout__owner=self.request.user)
).distinct()
return qs
# Details of comment
class CommentDetail(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [
permissions.IsAuthenticated & IsCommentVisibleToUser & (IsOwner | IsReadOnly)
]
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
# List of likes
class LikeList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = LikeSerializer
permission_classes = [permissions.IsAuthenticated]
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def get_queryset(self):
return Like.objects.filter(owner=self.request.user)
# Details of like
class LikeDetail(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
queryset = Like.objects.all()
serializer_class = LikeSerializer
permission_classes = [permissions.IsAuthenticated]
_Detail = []
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "secfit.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
python-3.8.6
\ No newline at end of file
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