diff --git a/.gitignore b/.gitignore index bdd4074d7d98ff4c226296bfaf9fd16a18e1283d..6b7798b33fe1313c5a1b6a98cc0b8351a771ad26 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ backend/secfit/.vscode/ backend/secfit/*/migrations/__pycache__/ backend/secfit/*/__pycache__/ backend/secfit/db.sqlite3 +.idea/ +Pipfile* + diff --git a/backend/secfit/facilities/admin.py b/backend/secfit/facilities/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..ee21d3079190c09c09096ee3f76355f131e33073 --- /dev/null +++ b/backend/secfit/facilities/admin.py @@ -0,0 +1,8 @@ +"""Module for registering facilities from facility app to admin page so that they appear +""" +from django.contrib import admin + +# Register your models here. +from .models import Facility + +admin.site.register(Facility) diff --git a/backend/secfit/facilities/apps.py b/backend/secfit/facilities/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..8acf7b1dd3b72a40bf90025661b743795f25ac95 --- /dev/null +++ b/backend/secfit/facilities/apps.py @@ -0,0 +1,13 @@ +"""AppConfig for facilities app +""" +from django.apps import AppConfig + + +class FacilitiesConfig(AppConfig): + """AppConfig for workouts app + + Attributes: + name (str): The name of the application + """ + + name = "facilities" \ No newline at end of file diff --git a/backend/secfit/facilities/migrations/0001_initial.py b/backend/secfit/facilities/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..926ced343530ff8b3a5e46615f3fa63d644f6d44 --- /dev/null +++ b/backend/secfit/facilities/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# Generated by Django 3.1 on 2021-02-25 13:57 + +import django.core.validators +from django.db import migrations, models +import facilities.models +import facilities.validators + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Facility', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50, unique=True)), + ('date', models.DateTimeField()), + ('description', models.CharField(max_length=400)), + ('latitude', models.DecimalField(decimal_places=5, max_digits=7, validators=[django.core.validators.MaxValueValidator(90), django.core.validators.MinValueValidator(-90)])), + ('longitude', models.DecimalField(decimal_places=5, max_digits=8, validators=[django.core.validators.MaxValueValidator(180), django.core.validators.MinValueValidator(-180)])), + ('image1', models.ImageField(upload_to=facilities.models.facility_directory_path, validators=[facilities.validators.image_size])), + ('image2', models.ImageField(upload_to=facilities.models.facility_directory_path, validators=[facilities.validators.image_size])), + ('image3', models.ImageField(upload_to=facilities.models.facility_directory_path, validators=[facilities.validators.image_size])), + ('image4', models.ImageField(upload_to=facilities.models.facility_directory_path, validators=[facilities.validators.image_size])), + ('image5', models.ImageField(upload_to=facilities.models.facility_directory_path, validators=[facilities.validators.image_size])), + ], + options={ + 'ordering': ['-date'], + }, + ), + ] diff --git a/backend/secfit/facilities/migrations/0002_auto_20210225_1508.py b/backend/secfit/facilities/migrations/0002_auto_20210225_1508.py new file mode 100644 index 0000000000000000000000000000000000000000..60901b2ce3bdab4d210206a4430829a440755bd3 --- /dev/null +++ b/backend/secfit/facilities/migrations/0002_auto_20210225_1508.py @@ -0,0 +1,45 @@ +# Generated by Django 3.1 on 2021-02-25 14:08 + +from django.db import migrations, models +import facilities.models +import facilities.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ('facilities', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='facility', + name='date', + field=models.DateField(), + ), + migrations.AlterField( + model_name='facility', + name='description', + field=models.TextField(validators=[facilities.validators.max_words]), + ), + migrations.AlterField( + model_name='facility', + name='image2', + field=models.ImageField(blank=True, upload_to=facilities.models.facility_directory_path, validators=[facilities.validators.image_size]), + ), + migrations.AlterField( + model_name='facility', + name='image3', + field=models.ImageField(blank=True, upload_to=facilities.models.facility_directory_path, validators=[facilities.validators.image_size]), + ), + migrations.AlterField( + model_name='facility', + name='image4', + field=models.ImageField(blank=True, upload_to=facilities.models.facility_directory_path, validators=[facilities.validators.image_size]), + ), + migrations.AlterField( + model_name='facility', + name='image5', + field=models.ImageField(blank=True, upload_to=facilities.models.facility_directory_path, validators=[facilities.validators.image_size]), + ), + ] diff --git a/backend/secfit/facilities/migrations/__init__.py b/backend/secfit/facilities/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/backend/secfit/facilities/models.py b/backend/secfit/facilities/models.py new file mode 100644 index 0000000000000000000000000000000000000000..fc58d6c17c68696d4694d60e51b59316f09f5640 --- /dev/null +++ b/backend/secfit/facilities/models.py @@ -0,0 +1,62 @@ +"""Contains the models for the facilities Django application. Admins create facilities, +which contain descriptions, images and location. +""" +import os +from django.core.validators import MaxValueValidator, MinValueValidator +from django.db import models + + +def facility_directory_path(instance, filename): + """Return path for which facility files should be uploaded on the web server + + Args: + instance (FacilityImage): FacilityImage instance + filename (str): Name of the file + + Returns: + str: Path where facility file is stored + """ + return f"facilities/{instance.name}/{filename}" + + +class Facility(models.Model): + """Django model for a facility that admins can add. + + A facility has several attributes, and is not associated + with other parts of the website (as stated in the requirements by our peer group) + + Attributes: + name: Name of the facility + date: Date the facility was added + description: Description of the facility + latitude: Latitude of the facility + longitude: Longitude of the facility + image1: Image of the facility + image2: Image of the facility + image3: Image of the facility + image4: Image of the facility + image5: Image of the facility + """ + from .validators import image_size, max_words + + name = models.CharField(max_length=50, unique=True) + date = models.DateField() + description = models.TextField(validators=[max_words]) + + latitude = models.DecimalField( + decimal_places=5, max_digits=7, validators=[MaxValueValidator(90), MinValueValidator(-90)]) + + longitude = models.DecimalField( + decimal_places=5, max_digits=8, validators=[MaxValueValidator(180), MinValueValidator(-180)]) + + image1 = models.ImageField(validators=[image_size], upload_to=facility_directory_path) + image2 = models.ImageField(validators=[image_size], upload_to=facility_directory_path, blank=True) + image3 = models.ImageField(validators=[image_size], upload_to=facility_directory_path, blank=True) + image4 = models.ImageField(validators=[image_size], upload_to=facility_directory_path, blank=True) + image5 = models.ImageField(validators=[image_size], upload_to=facility_directory_path, blank=True) + + class Meta: + ordering = ["-date"] + + def __str__(self): + return self.name \ No newline at end of file diff --git a/backend/secfit/facilities/urls.py b/backend/secfit/facilities/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..0948dfaa9674a5b09608b34fe3499a87c7767a7f --- /dev/null +++ b/backend/secfit/facilities/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from facilities import views +from rest_framework.urlpatterns import format_suffix_patterns + +urlpatterns = [ + path("", views.api_root), + path("api/facilities/", views.FacilityList.as_view(), name="facility-list"), + path("api/facilities//", views.FacilitySingle.as_view(), name="facility-detail"), + path("api/facilities//", views.FacilitySingle.as_view(), name="facility-detail"), +] diff --git a/backend/secfit/facilities/validators.py b/backend/secfit/facilities/validators.py new file mode 100644 index 0000000000000000000000000000000000000000..17250bfcb038a8f3002f00595b32662cd120f4ce --- /dev/null +++ b/backend/secfit/facilities/validators.py @@ -0,0 +1,12 @@ +from django.core.exceptions import ValidationError + + +def image_size(image): + if image.file.size > 3 * 1024 * 1024: + raise ValidationError('Image size needs to be less than 3MB') + + +# Restricts the amount of words to 500 +def max_words(string): + if len(string.split(" ")) > 500: + raise ValidationError('Description can contain a maximum of 500 words') diff --git a/backend/secfit/facilities/views.py b/backend/secfit/facilities/views.py new file mode 100644 index 0000000000000000000000000000000000000000..1faea73e3e42579894b7d4f901f73c010c5a4181 --- /dev/null +++ b/backend/secfit/facilities/views.py @@ -0,0 +1,65 @@ +"""Contains views for the workouts application. These are mostly class-based views. +""" +from rest_framework import generics, mixins +from rest_framework import permissions +from rest_framework.decorators import api_view +from rest_framework.reverse import reverse +from facilities.models import Facility +from rest_framework.response import Response + + +@api_view(["GET"]) +def api_root(request, format=None): + return Response( + { + "users": reverse("user-list", request=request, format=format), + "facilities": reverse("facility-list", request=request, format=format), + } + ) + + +class FacilityGeneration( + mixins.CreateModelMixin, generics.GenericAPIView +): + """Class defining the web response for creation of a facility + + HTTP methods: POST + """ + + queryset = Facility.objects.all() + permission_classes = [permissions.IsAdminUser] + + def post(self, request, *args, **kwargs): + return self.post(request, *args, **kwargs) + + +class FacilityList( + mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView +): + """Class defining the web response for retrieval of multiple facilities + + HTTP methods: GET + """ + + queryset = Facility.objects.all() + permission_classes = [permissions.IsAuthenticated] + ordering_fields = ["name", "date"] + + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) + + +class FacilitySingle( + mixins.RetrieveModelMixin, + generics.GenericAPIView, +): + """Class defining the web response for the details of an individual Facility. + + HTTP methods: GET + """ + + queryset = Facility.objects.all() + permission_classes = [permissions.IsAuthenticated] + + def get(self, request, *args, **kwargs): + return self.retrieve(request, *args, **kwargs) \ No newline at end of file diff --git a/backend/secfit/requirements.txt b/backend/secfit/requirements.txt index 9feb375bde1e8fb7befe6c102dd29beeee7c6940..aa9c4c91c421043439b74f82ef7f18416f81eeea 100644 Binary files a/backend/secfit/requirements.txt and b/backend/secfit/requirements.txt differ diff --git a/backend/secfit/secfit/settings.py b/backend/secfit/secfit/settings.py index 92336536fbb75beeade6bfc8e60eb393531832c9..54622f9576d4645de61bba91a488c8fe32eefdc4 100644 --- a/backend/secfit/secfit/settings.py +++ b/backend/secfit/secfit/settings.py @@ -58,6 +58,7 @@ INSTALLED_APPS = [ "workouts.apps.WorkoutsConfig", "users.apps.UsersConfig", "comments.apps.CommentsConfig", + "facilities.apps.FacilitiesConfig", "corsheaders", ] diff --git a/backend/secfit/secfit/urls.py b/backend/secfit/secfit/urls.py index 3146886ed88c67c7f8838c74cea38c7b7ee5555a..900cd7ac60e08eeb18d144d55b181bdc5855697e 100644 --- a/backend/secfit/secfit/urls.py +++ b/backend/secfit/secfit/urls.py @@ -21,6 +21,7 @@ from django.conf.urls.static import static urlpatterns = [ path("admin/", admin.site.urls), path("", include("workouts.urls")), + path("", include("facilities.urls")), ] urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)