Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions backend/apps/volontulo/tests/views/api/offers/test_join.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-

"""
.. module:: test_join
"""

from rest_framework import status
from rest_framework.test import APITestCase

from apps.volontulo.tests.views.offers.commons import TestOffersCommons


class TestAuthenticatedUserJoinOffer(TestOffersCommons, APITestCase):

"""Tests for REST API's join offer view for authenticated user."""

def setUp(self):
"""Set up each test."""
super(TestAuthenticatedUserJoinOffer, self).setUp()
self.client.login(username='admin@example.com', password='123admin')

def test_user_join_offer_authenticated(self):
"""Test offer's created status for admin user."""
response = self.client.post('/api/offers/1/join/')

self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def test_user_join_not_existing_offer(self):
"""Test offer's 404 status for non existing offer."""
response = self.client.post('/api/offers/1999999999999/join/')

self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)


class TestNotAuthenticatedUserJoinOffer(TestOffersCommons, APITestCase):

"""Tests for REST API's join offer view for not authenitcated user"""

def test_user_join_offer_not_authenticated(self):
"""Test offer's forbidden status for not logged in user."""
response = self.client.post('/api/offers/1/join/')

self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
12 changes: 12 additions & 0 deletions backend/apps/volontulo/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,18 @@ def get_queryset(self):
)
return qs.filter(offer_status='published')

@detail_route(methods=['POST'], permission_classes=(IsAuthenticated,))
# pylint: disable=invalid-name
def join(self, request, pk):
"""Endpoint to join offer by current user"""
offer = get_object_or_404(self.get_queryset(), id=pk)
offer.volunteers.add(request.user)

return Response(self.serializer_class(
offer,
context={'request': request}
).data, status=201)


class OrganizationViewSet(viewsets.ModelViewSet):

Expand Down
2 changes: 1 addition & 1 deletion frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { ContactResolver } from './resolvers';
import { FormErrorComponent } from './form-error/form-error.component';
import { ContactService } from './contact.service';
import { UserProfileComponent } from './user-profile/user-profile.component';
import { OfferJoinFormComponent } from './offers/offer-join-form/offer-join-form.component';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';

Raven.config(environment.sentryDSN).install();
Expand Down Expand Up @@ -166,6 +167,11 @@ const appRoutes: Routes = [
component: UserProfileComponent,
canActivate: [LoggedInGuard],
},
{
path: 'offers/:offerSlug/:offerId/join',
component: OfferJoinFormComponent,
canActivate: [LoggedInGuard],
},
{
path: '**',
component: RedirectComponent
Expand Down Expand Up @@ -212,7 +218,8 @@ registerLocaleData(localePl);
ContactComponent,
FormErrorComponent,
OrganizationsListComponent,
UserProfileComponent
UserProfileComponent,
OfferJoinFormComponent,
],
imports: [
BrowserModule.withServerTransition({ appId: 'volontulo' }),
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/app/homepage-offer/offers.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ export class OffersService {
return this.http.put(`${environment.apiRoot}/offers/${id}/`, offer);
}

joinOffer(id: number, message: string) {
return this.http.post(`${environment.apiRoot}/offers/${id}/join/`, {message}, {observe: 'response'});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ <h2>Pomagasz już w tej inicjatywie</h2>
<div class="alert alert-danger">
<h2>Możesz pomóc?</h2>
<p>Twoja pomoc jest ważna. Potrzebujemy Ciebie!</p>
<a class="btn btn-light" href="{{ getJoinViewUrl(offer) }}"
<a [routerLink]="['/offers', offer.slug, offer.id,'join']" class="btn btn-light"
>Zgłoś się na ten wolontariat</a>
</div>
</ng-template>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div class="container">
<form [formGroup]="joinForm" (ngSubmit)="onSubmit()">
<div class="form-group row">
<label class="col-form-label" for="{{ email }}">Email:</label>
<div class="col-md-5">
<input disabled class="form-control" formControlName="applicant_email" id="{{ email }}"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You use {{ }} to bind to email variable, that is not defined in the component, so as a result, you get empty for attributes in HTML (same problem for all other labels). Define them just using strings, and if you define for attribute in a label don't forget to define id' in input`, so focus can be set after clicking on label.

</div>
<label class="col-form-label" for="{{ fullname }}">Imię i nazwisko:</label>
<div class="col-md-5">
<input disabled class="form-control" formControlName="applicant_name" id="{{ fullname }}"/>
</div>
</div>
<div class="form-group row">
<label class="col-form-label" for="{{ phone }}">Telefon:</label>
<div class="col-md-5">
<input disabled class="form-control" formControlName="phone_no"/>
</div>
</div>
<div class="form-group-row">
<label class="col-md-3 col-form-label" for="{{ message }}">Możliwe uwagi:</label>
<div class="col-md-9">
<textarea class="form-control" rows="3" formControlName="message"></textarea>
<volontulo-form-error [fc]="joinForm.controls.message" [minLength]="10" [maxLength]="2000"></volontulo-form-error>
</div>
</div>
<div class="form-group row">
<div>
<button type="submit" class="btn-primary">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missed btn class

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
This form doesn't look good - smth is wrong with grid and RWD doesn't work as well.

Wyślij
</button>
</div>
</div>
</form>
</div>

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {HttpClient} from '@angular/common/http';
import {Location} from '@angular/common'

import {ActivatedRoute} from '@angular/router';
import {AuthService} from '../../auth.service';
import {OffersService} from 'app/homepage-offer/offers.service';
import {User} from '../../user';
import {UserService} from '../../user.service';

@Component({
selector: 'volontulo-offer-join-form',
templateUrl: './offer-join-form.component.html'
})

export class OfferJoinFormComponent implements OnInit {
public joinForm: FormGroup = this.fb.group({
applicant_email: [''],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO camelCase will be better for TypeScript.

applicant_name: [''],
message: ['', [Validators.minLength(10), Validators.maxLength(2000)]],
phone_no: [''],
});

public submitEnabled = false;
public success: null | boolean = null;
public offerId: number;
public error;

constructor(
private activatedRoute: ActivatedRoute,
private fb: FormBuilder,
private authService: AuthService,
private userService: UserService,
private httpClient: HttpClient,
private offersService: OffersService,
private location: Location ,
) {
}

ngOnInit() {
this.authService.user$
.subscribe(
(user: User) => {
this.joinForm.controls.applicant_email.setValue(user.email);
this.joinForm.controls.applicant_name.setValue(this.userService.getFullName(user));
this.joinForm.controls.phone_no.setValue(user.phoneNo);
}
);

this.activatedRoute.params
.switchMap(params => this.offerId = params.offerId)
.subscribe()
}

onSubmit() {
if (this.joinForm.valid) {
this.submitEnabled = true;

this.offersService.joinOffer(this.offerId, this.joinForm.value.message).subscribe(
response => {
if (response.status === 201) {
this.location.back()}}
)
}
}
}