diff --git a/README.md b/README.md index f366789c6e4e2f3586ef0b3c31217620302a1976..8912dda2338b920d2efa185d68aab253e53b2490 100644 --- a/README.md +++ b/README.md @@ -126,29 +126,3 @@ If you want to run this as a mobile application 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 - -##Tests - -### Django - -To run test for backend enter -`python manage.py test` - - -With coverage: -`coverage run manage.py test` - -`coverage report -m ./users/serializers.py ./workouts/permissions.py` - - -### Cypress - -To run the test in cypress go to the frontend folder and enter in different terminals: - -`npm run test:dev` - -`npm run django:testserver` - -`npm run cypress:open` - - diff --git a/backend/secfit/comments/migrations/0002_auto_20210304_2241.py b/backend/secfit/comments/migrations/0002_auto_20210304_2241.py new file mode 100644 index 0000000000000000000000000000000000000000..0c4fb6d46f3d27664651edc221a883f2e7a01888 --- /dev/null +++ b/backend/secfit/comments/migrations/0002_auto_20210304_2241.py @@ -0,0 +1,24 @@ +# Generated by Django 3.1 on 2021-03-04 22:41 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('comments', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='comment', + name='timestamp', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='like', + name='timestamp', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + ] diff --git a/backend/secfit/suggested_workouts/migrations/0002_auto_20210304_2241.py b/backend/secfit/suggested_workouts/migrations/0002_auto_20210304_2241.py new file mode 100644 index 0000000000000000000000000000000000000000..5da3bc244f4fbc5bcb4a4faf37c5cffcc8deed65 --- /dev/null +++ b/backend/secfit/suggested_workouts/migrations/0002_auto_20210304_2241.py @@ -0,0 +1,30 @@ +# Generated by Django 3.1 on 2021-03-04 22:41 + +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), + ('suggested_workouts', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='suggestedworkout', + name='visibility', + ), + migrations.AddField( + model_name='suggestedworkout', + name='status', + field=models.CharField(choices=[('a', 'Accepted'), ('p', 'Pending'), ('d', 'Declined')], default='p', max_length=8), + ), + migrations.AlterField( + model_name='suggestedworkout', + name='coach', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owner', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/backend/secfit/suggested_workouts/migrations/0003_suggestedworkout_visibility.py b/backend/secfit/suggested_workouts/migrations/0003_suggestedworkout_visibility.py new file mode 100644 index 0000000000000000000000000000000000000000..d2910f0674211fb44aed07a9c21dbd28847a0250 --- /dev/null +++ b/backend/secfit/suggested_workouts/migrations/0003_suggestedworkout_visibility.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1 on 2021-03-04 22:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('suggested_workouts', '0002_auto_20210304_2241'), + ] + + operations = [ + migrations.AddField( + model_name='suggestedworkout', + name='visibility', + field=models.CharField(default='PU', max_length=8), + ), + ] diff --git a/backend/secfit/suggested_workouts/migrations/0004_remove_suggestedworkout_visibility.py b/backend/secfit/suggested_workouts/migrations/0004_remove_suggestedworkout_visibility.py new file mode 100644 index 0000000000000000000000000000000000000000..b954002b01399a9197dbd879f89bf3e0e250e09d --- /dev/null +++ b/backend/secfit/suggested_workouts/migrations/0004_remove_suggestedworkout_visibility.py @@ -0,0 +1,17 @@ +# Generated by Django 3.1 on 2021-03-04 23:00 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('suggested_workouts', '0003_suggestedworkout_visibility'), + ] + + operations = [ + migrations.RemoveField( + model_name='suggestedworkout', + name='visibility', + ), + ] diff --git a/backend/secfit/suggested_workouts/migrations/0005_suggestedworkout_visibility.py b/backend/secfit/suggested_workouts/migrations/0005_suggestedworkout_visibility.py new file mode 100644 index 0000000000000000000000000000000000000000..782a3ccc84eeb87085b6168e2b1101a7ae8a1e7b --- /dev/null +++ b/backend/secfit/suggested_workouts/migrations/0005_suggestedworkout_visibility.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1 on 2021-03-04 23:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('suggested_workouts', '0004_remove_suggestedworkout_visibility'), + ] + + operations = [ + migrations.AddField( + model_name='suggestedworkout', + name='visibility', + field=models.CharField(blank=True, default='', max_length=8, null=True), + ), + ] diff --git a/backend/secfit/suggested_workouts/migrations/0006_auto_20210305_0929.py b/backend/secfit/suggested_workouts/migrations/0006_auto_20210305_0929.py new file mode 100644 index 0000000000000000000000000000000000000000..3daeb6a801389f183a47dffddb763e489c18c195 --- /dev/null +++ b/backend/secfit/suggested_workouts/migrations/0006_auto_20210305_0929.py @@ -0,0 +1,20 @@ +# Generated by Django 3.1 on 2021-03-05 09:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('suggested_workouts', '0005_suggestedworkout_visibility'), + ] + + operations = [ + migrations.RemoveField( + model_name='suggestedworkout', + name='visibility', + ), + migrations.DeleteModel( + name='WorkoutRequest', + ), + ] diff --git a/backend/secfit/users/migrations/0002_auto_20210304_2241.py b/backend/secfit/users/migrations/0002_auto_20210304_2241.py new file mode 100644 index 0000000000000000000000000000000000000000..007428eed8563af6bd93de6878709da7a3ea4ae7 --- /dev/null +++ b/backend/secfit/users/migrations/0002_auto_20210304_2241.py @@ -0,0 +1,82 @@ +# Generated by Django 3.1 on 2021-03-04 22:41 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import users.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='AthleteFile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('file', models.FileField(upload_to=users.models.athlete_directory_path)), + ], + ), + migrations.RemoveField( + model_name='offer', + name='offer_type', + ), + migrations.AddField( + model_name='offer', + name='status', + field=models.CharField(choices=[('a', 'Accepted'), ('p', 'Pending'), ('d', 'Declined')], default='p', max_length=8), + ), + migrations.AddField( + model_name='offer', + name='timestamp', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='user', + name='city', + field=models.TextField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='user', + name='country', + field=models.TextField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='user', + name='phone_number', + field=models.TextField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='user', + name='street_address', + field=models.TextField(blank=True, max_length=50), + ), + migrations.AlterField( + model_name='offer', + name='recipient', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_offers', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='user', + name='coach', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='athletes', to=settings.AUTH_USER_MODEL), + ), + migrations.DeleteModel( + name='OfferResponse', + ), + migrations.AddField( + model_name='athletefile', + name='athlete', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='coach_files', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='athletefile', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='athlete_files', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/backend/secfit/workouts/migrations/0002_auto_20210304_2241.py b/backend/secfit/workouts/migrations/0002_auto_20210304_2241.py new file mode 100644 index 0000000000000000000000000000000000000000..739d4979daf258fd940a9720d18555f0ece5b077 --- /dev/null +++ b/backend/secfit/workouts/migrations/0002_auto_20210304_2241.py @@ -0,0 +1,54 @@ +# Generated by Django 3.1 on 2021-03-04 22:41 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('suggested_workouts', '0002_auto_20210304_2241'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('workouts', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='RememberMe', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('remember_me', models.CharField(max_length=500)), + ], + ), + migrations.AddField( + model_name='exerciseinstance', + name='suggested_workout', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='suggested_exercise_instances', to='suggested_workouts.suggestedworkout'), + ), + migrations.AddField( + model_name='workout', + name='planned', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='workoutfile', + name='suggested_workout', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='suggested_workout_files', to='suggested_workouts.suggestedworkout'), + ), + migrations.AlterField( + model_name='exerciseinstance', + name='workout', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='exercise_instances', to='workouts.workout'), + ), + migrations.AlterField( + model_name='workoutfile', + name='owner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='workout_files', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='workoutfile', + name='workout', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='files', to='workouts.workout'), + ), + ] diff --git a/backend/secfit/workouts/views.py b/backend/secfit/workouts/views.py index 1250cf3503436a4a30f5fc8114e7eaf318773793..26254774232a1baa7737a3f36a476dd0a083f4fc 100644 --- a/backend/secfit/workouts/views.py +++ b/backend/secfit/workouts/views.py @@ -142,9 +142,7 @@ class WorkoutList( # - The workout has coach visibility and the requesting user is the owner's coach qs = Workout.objects.filter( Q(visibility="PU") - | Q(owner=self.request.user) | (Q(visibility="CO") & Q(owner__coach=self.request.user)) - | (Q(visibility= "PR") & Q(owner=self.request.user)) ).distinct() # Check if the planned workout has happened if len(qs) > 0: diff --git a/frontend/.gitignore b/frontend/.gitignore index afbcf7b942bc10475416a902e347474eedbea14e..103acd42ec68a9862973a8a7f221a9aaa56eac16 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -6,4 +6,3 @@ node_modules/ # Generated by Cordova #/plugins/ platforms/browser/www/ - diff --git a/frontend/cypress.json b/frontend/cypress.json deleted file mode 100644 index fb073120c7ac922c8de818e6545923f70053064a..0000000000000000000000000000000000000000 --- a/frontend/cypress.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "baseUrl": "http://localhost:8001" -} diff --git a/frontend/cypress/fixtures/example.json b/frontend/cypress/fixtures/example.json deleted file mode 100644 index da18d9352a17d427321962199a1fa43b8ab5cfe4..0000000000000000000000000000000000000000 --- a/frontend/cypress/fixtures/example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" -} \ No newline at end of file diff --git a/frontend/cypress/fixtures/seed.json b/frontend/cypress/fixtures/seed.json deleted file mode 100644 index 32c13669406955b9fe8d6ceae493c9eacd1fcf41..0000000000000000000000000000000000000000 --- a/frontend/cypress/fixtures/seed.json +++ /dev/null @@ -1,29 +0,0 @@ -[ - { - "model": "workouts.exercise", - "pk": 1, - "fields": { - "name": "Push-up", - "description": "A push-up (or press-up in British English) is a common calisthenics exercise beginning from the prone position.", - "unit": "reps" - } - }, - { - "model": "workouts.exercise", - "pk": 2, - "fields": { - "name": "Crunch", - "description": "The crunch is one of the most popular abdominal exercises.", - "unit": "reps" - } - }, - { - "model": "workouts.exercise", - "pk": 3, - "fields": { - "name": "Plank", - "description": "The plank is an isometric core strength exercise that involves maintaining a position similar to a push-up for the maximum possible time.", - "unit": "seconds" - } - } - ] \ No newline at end of file diff --git a/frontend/cypress/fixtures/suggestedWorkout.json b/frontend/cypress/fixtures/suggestedWorkout.json deleted file mode 100644 index 9e0a247bebea6e5275bdac003fcf435b29a071ce..0000000000000000000000000000000000000000 --- a/frontend/cypress/fixtures/suggestedWorkout.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "model": "suggested_workouts.suggestedworkout", - "pk": 1, - "fields": { - "name": "Suggested Workout Seed", - "date": null, - "notes": "heiheihei", - "coach": 1, - "athlete": 2, - "status": "p" - } - } -] \ No newline at end of file diff --git a/frontend/cypress/fixtures/users.json b/frontend/cypress/fixtures/users.json deleted file mode 100644 index d980c0b9e3757ef9af88958472383dc5e122487e..0000000000000000000000000000000000000000 --- a/frontend/cypress/fixtures/users.json +++ /dev/null @@ -1,85 +0,0 @@ -[ - { - "model": "users.user", - "pk": 1, - "fields": { - "password": "pbkdf2_sha256$216000$z0k3ideAq6Zn$mlC8XmI/+IFigImw7SGhQ+ffTutLvbtCcewgDgfmzMg=", - "last_login": null, - "is_superuser": false, - "username": "coach", - "first_name": "", - "last_name": "", - "email": "coach@hei.no", - "is_staff": false, - "is_active": true, - "date_joined": "2021-03-06T13:30:38.062Z", - "coach": null, - "phone_number": "90909", - "country": "uuu", - "city": "uuu", - "street_address": "uu", - "groups": [], - "user_permissions": [] - } - }, - { - "model": "users.user", - "pk": 2, - "fields": { - "password": "pbkdf2_sha256$216000$cDkq1l1xAvqV$C/FxUDUzeqMSpc7PuaVwImucY6cFM/Ju9CMguJTclJQ=", - "last_login": null, - "is_superuser": false, - "username": "athlete", - "first_name": "", - "last_name": "", - "email": "athlete@hei.no", - "is_staff": false, - "is_active": true, - "date_joined": "2021-03-06T13:30:56.670Z", - "coach": 1, - "phone_number": "99", - "country": "hh", - "city": "hhh", - "street_address": "h", - "groups": [], - "user_permissions": [] - } - }, - { - "model": "users.offer", - "pk": 1, - "fields": { - "owner": 1, - "recipient": 2, - "status": "d", - "timestamp": "2021-03-06T13:31:08.471Z" - } - }, - { - "model": "workouts.exercise", - "pk": 1, - "fields": { - "name": "Push-up", - "description": "A push-up (or press-up in British English) is a common calisthenics exercise beginning from the prone position.", - "unit": "reps" - } - }, - { - "model": "workouts.exercise", - "pk": 2, - "fields": { - "name": "Crunch", - "description": "The crunch is one of the most popular abdominal exercises.", - "unit": "reps" - } - }, - { - "model": "workouts.exercise", - "pk": 3, - "fields": { - "name": "Plank", - "description": "The plank is an isometric core strength exercise that involves maintaining a position similar to a push-up for the maximum possible time.", - "unit": "seconds" - } - } -] diff --git a/frontend/cypress/fixtures/workouts.json b/frontend/cypress/fixtures/workouts.json deleted file mode 100644 index 56aeb5fc4b329a5df9417c7bb0ce0fe7230104c8..0000000000000000000000000000000000000000 --- a/frontend/cypress/fixtures/workouts.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "model": "workouts.workout", - "pk": 1, - "fields": { - "name": "Workout Private - Coach", - "date": "2021-03-01T23:03:00Z", - "notes": "Hei", - "owner": 1, - "planned": false, - "visibility": "PR" - } - }, - { - "model": "workouts.workout", - "pk": 2, - "fields": { - "name": "Workout Visible for Coach", - "date": "2021-03-01T23:05:00Z", - "notes": "dddd", - "owner": 2, - "planned": false, - "visibility": "CO" - } - }, - { - "model": "workouts.workout", - "pk": 3, - "fields": { - "name": "Workout Public - Coach", - "date": "2021-03-06T23:09:00Z", - "notes": "dddd", - "owner": 1, - "planned": false, - "visibility": "PU" - } - } -] \ No newline at end of file diff --git a/frontend/cypress/integration/examples/actions.spec.js b/frontend/cypress/integration/examples/actions.spec.js deleted file mode 100644 index 092637998e28e31357cb95102f50b2e779db6430..0000000000000000000000000000000000000000 --- a/frontend/cypress/integration/examples/actions.spec.js +++ /dev/null @@ -1,299 +0,0 @@ -/// - -context('Actions', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/actions') - }) - - // https://on.cypress.io/interacting-with-elements - - it('.type() - type into a DOM element', () => { - // https://on.cypress.io/type - cy.get('.action-email') - .type('fake@email.com').should('have.value', 'fake@email.com') - - // .type() with special character sequences - .type('{leftarrow}{rightarrow}{uparrow}{downarrow}') - .type('{del}{selectall}{backspace}') - - // .type() with key modifiers - .type('{alt}{option}') //these are equivalent - .type('{ctrl}{control}') //these are equivalent - .type('{meta}{command}{cmd}') //these are equivalent - .type('{shift}') - - // Delay each keypress by 0.1 sec - .type('slow.typing@email.com', { delay: 100 }) - .should('have.value', 'slow.typing@email.com') - - cy.get('.action-disabled') - // Ignore error checking prior to type - // like whether the input is visible or disabled - .type('disabled error checking', { force: true }) - .should('have.value', 'disabled error checking') - }) - - it('.focus() - focus on a DOM element', () => { - // https://on.cypress.io/focus - cy.get('.action-focus').focus() - .should('have.class', 'focus') - .prev().should('have.attr', 'style', 'color: orange;') - }) - - it('.blur() - blur off a DOM element', () => { - // https://on.cypress.io/blur - cy.get('.action-blur').type('About to blur').blur() - .should('have.class', 'error') - .prev().should('have.attr', 'style', 'color: red;') - }) - - it('.clear() - clears an input or textarea element', () => { - // https://on.cypress.io/clear - cy.get('.action-clear').type('Clear this text') - .should('have.value', 'Clear this text') - .clear() - .should('have.value', '') - }) - - it('.submit() - submit a form', () => { - // https://on.cypress.io/submit - cy.get('.action-form') - .find('[type="text"]').type('HALFOFF') - - cy.get('.action-form').submit() - .next().should('contain', 'Your form has been submitted!') - }) - - it('.click() - click on a DOM element', () => { - // https://on.cypress.io/click - cy.get('.action-btn').click() - - // You can click on 9 specific positions of an element: - // ----------------------------------- - // | topLeft top topRight | - // | | - // | | - // | | - // | left center right | - // | | - // | | - // | | - // | bottomLeft bottom bottomRight | - // ----------------------------------- - - // clicking in the center of the element is the default - cy.get('#action-canvas').click() - - cy.get('#action-canvas').click('topLeft') - cy.get('#action-canvas').click('top') - cy.get('#action-canvas').click('topRight') - cy.get('#action-canvas').click('left') - cy.get('#action-canvas').click('right') - cy.get('#action-canvas').click('bottomLeft') - cy.get('#action-canvas').click('bottom') - cy.get('#action-canvas').click('bottomRight') - - // .click() accepts an x and y coordinate - // that controls where the click occurs :) - - cy.get('#action-canvas') - .click(80, 75) // click 80px on x coord and 75px on y coord - .click(170, 75) - .click(80, 165) - .click(100, 185) - .click(125, 190) - .click(150, 185) - .click(170, 165) - - // click multiple elements by passing multiple: true - cy.get('.action-labels>.label').click({ multiple: true }) - - // Ignore error checking prior to clicking - cy.get('.action-opacity>.btn').click({ force: true }) - }) - - it('.dblclick() - double click on a DOM element', () => { - // https://on.cypress.io/dblclick - - // Our app has a listener on 'dblclick' event in our 'scripts.js' - // that hides the div and shows an input on double click - cy.get('.action-div').dblclick().should('not.be.visible') - cy.get('.action-input-hidden').should('be.visible') - }) - - it('.rightclick() - right click on a DOM element', () => { - // https://on.cypress.io/rightclick - - // Our app has a listener on 'contextmenu' event in our 'scripts.js' - // that hides the div and shows an input on right click - cy.get('.rightclick-action-div').rightclick().should('not.be.visible') - cy.get('.rightclick-action-input-hidden').should('be.visible') - }) - - it('.check() - check a checkbox or radio element', () => { - // https://on.cypress.io/check - - // By default, .check() will check all - // matching checkbox or radio elements in succession, one after another - cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]') - .check().should('be.checked') - - cy.get('.action-radios [type="radio"]').not('[disabled]') - .check().should('be.checked') - - // .check() accepts a value argument - cy.get('.action-radios [type="radio"]') - .check('radio1').should('be.checked') - - // .check() accepts an array of values - cy.get('.action-multiple-checkboxes [type="checkbox"]') - .check(['checkbox1', 'checkbox2']).should('be.checked') - - // Ignore error checking prior to checking - cy.get('.action-checkboxes [disabled]') - .check({ force: true }).should('be.checked') - - cy.get('.action-radios [type="radio"]') - .check('radio3', { force: true }).should('be.checked') - }) - - it('.uncheck() - uncheck a checkbox element', () => { - // https://on.cypress.io/uncheck - - // By default, .uncheck() will uncheck all matching - // checkbox elements in succession, one after another - cy.get('.action-check [type="checkbox"]') - .not('[disabled]') - .uncheck().should('not.be.checked') - - // .uncheck() accepts a value argument - cy.get('.action-check [type="checkbox"]') - .check('checkbox1') - .uncheck('checkbox1').should('not.be.checked') - - // .uncheck() accepts an array of values - cy.get('.action-check [type="checkbox"]') - .check(['checkbox1', 'checkbox3']) - .uncheck(['checkbox1', 'checkbox3']).should('not.be.checked') - - // Ignore error checking prior to unchecking - cy.get('.action-check [disabled]') - .uncheck({ force: true }).should('not.be.checked') - }) - - it('.select() - select an option in a