diff --git a/.gitignore b/.gitignore index 21b11eb4e4a540f1150e1bd20110e13299dba0d9..2c929c8ef089cbe463e83ad29a9633ff7ecee602 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ __pycache__/ .python-version .coverage .env_var -static \ No newline at end of file +static +env +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..e723c48a27c6603bfdad04bd9c61d1176731d050 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.8-slim-buster +ENV PYTHONUNBUFFERED=1 +WORKDIR /code +COPY requirements.txt /code/ +RUN pip3 install -r requirements.txt +COPY . /code/ +COPY wait-for-it.sh /wait-for-it.sh +RUN chmod +x /wait-for-it.sh \ No newline at end of file diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 25dd94b55994c8f8b19002bbdbacc2e5ea5870ba..715d6067caaa544a0000d35a687999c0c54dc6cc --- a/README.md +++ b/README.md @@ -1,126 +1,135 @@ -# Home Industry API - -[![pipeline status](https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2020/ppl-c/diskominfo-depok-tpu-online/post-rpl-backend/badges/master/pipeline.svg)](https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2020/ppl-c/diskominfo-depok-tpu-online/post-rpl-backend/commits/master) -[![coverage report](https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2020/ppl-c/diskominfo-depok-tpu-online/post-rpl-backend/badges/master/coverage.svg)](https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2020/ppl-c/diskominfo-depok-tpu-online/post-rpl-backend/commits/master) - -## Table of Contents - -- [Environment Variables](#environment-variables) - - [Local](#local) - - [CI](#ci) - - [Staging](#staging) - - [Production](#production) -- [Local Configuration](#local-configuration) -- [Deployed API URLs](#deployed-api-urls) -- [API Documentation](#api-documentation) - -## Environment Variables - -### Local - -Key | Required | Example ---- | --- | --- -`DATABASE_HOST` | yes | `127.0.0.1` -`DATABASE_NAME` | yes | `home_industry` -`DATABASE_PASSWORD` | yes | `postgres` -`DATABASE_PORT` | yes | `5432` -`DATABASE_USER` | yes | `postgres` -`DEBUG` | no | `True` -`DJANGO_SETTINGS_MODULE` | yes | `home_industry.settings.local` -`SECRET_KEY` | yes | `7&s33ax$lxxzti1)0y=8#tu!$7bdy)p$1@kn06tp&8x8i9#h2u` - -### CI - -Key | Required | Example ---- | --- | --- -`DJANGO_SETTINGS_MODULE` | yes | `home_industry.settings.ci` -`SECRET_KEY` | yes | `7&s33ax$lxxzti1)0y=8#tu!$7bdy)p$1@kn06tp&8x8i9#h2u` - -### Staging - -Key | Required | Example ---- | --- | --- -`AWS_ACCESS_KEY_ID` | yes | `AKIAIOSFODNN7EXAMPLE` -`AWS_REGION_NAME` | yes | `ap-southeast-1` -`AWS_SECRET_ACCESS_KEY` | yes | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` -`AWS_STORAGE_BUCKET_NAME` | yes | `homeindustry-api` -`DATABASE_URL` | yes | `postgres://postgres:postgres@ec2-117-21-174-214.compute-1.amazonaws.com:5432/home_industry` -`DEBUG` | no | `True` -`DJANGO_SETTINGS_MODULE` | yes | `home_industry.settings.staging` -`HOME_INDUSTRY_ADMIN_SITE_URL` | no | `https://homeindustry.com/admin/` -`HOME_INDUSTRY_ADMIN_SITE_USER_PATH` | no | `users/` -`HOME_INDUSTRY_ADMIN_SITE_PRODUCT_PATH` | no | `products/` -`HOME_INDUSTRY_ADMIN_SITE_TRANSACTION_PATH` | no | `transactions/` -`HOME_INDUSTRY_ADMIN_SITE_PROGRAM_PATH` | no | `programs/` -`HOME_INDUSTRY_ADMIN_SITE_PROGRAM_DONATION_PATH` | no | `program-donations/` -`SECRET_KEY` | yes | `7&s33ax$lxxzti1)0y=8#tu!$7bdy)p$1@kn06tp&8x8i9#h2u` - -### Production - -Key | Required | Example ---- | --- | --- -`AWS_ACCESS_KEY_ID` | yes | `AKIAIOSFODNN7EXAMPLE` -`AWS_REGION_NAME` | yes | `ap-southeast-1` -`AWS_SECRET_ACCESS_KEY` | yes | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` -`AWS_STORAGE_BUCKET_NAME` | yes | `homeindustry-api` -`DATABASE_HOST` | yes | `127.0.0.1` -`DATABASE_NAME` | yes | `home_industry` -`DATABASE_PASSWORD` | yes | `postgres` -`DATABASE_PORT` | yes | `5432` -`DATABASE_USER` | yes | `postgres` -`DJANGO_SETTINGS_MODULE` | yes | `home_industry.settings.production` -`HOME_INDUSTRY_ADMIN_SITE_URL` | no | `https://homeindustry.com/admin/` -`HOME_INDUSTRY_ADMIN_SITE_USER_PATH` | no | `users/` -`HOME_INDUSTRY_ADMIN_SITE_PRODUCT_PATH` | no | `products/` -`HOME_INDUSTRY_ADMIN_SITE_TRANSACTION_PATH` | no | `transactions/` -`HOME_INDUSTRY_ADMIN_SITE_PROGRAM_PATH` | no | `programs/` -`HOME_INDUSTRY_ADMIN_SITE_PROGRAM_DONATION_PATH` | no | `program-donations/` -`SECRET_KEY` | yes | `7&s33ax$lxxzti1)0y=8#tu!$7bdy)p$1@kn06tp&8x8i9#h2u` - -## Local Configuration - -- Install Python 3.6 or higher and PostgreSQL -- Create Python virtual environment - ```bash - $ cd /path/to/project/directory - $ python3 -m venv env - $ source env/bin/activate - $ pip3 install -r requirements.txt - ``` -- Create PostgreSQL database -- Set up environment variables (change as needed) - ```bash - $ export DATABASE_HOST='127.0.0.1' - $ export DATABASE_NAME='home_industry' - $ export DATABASE_PASSWORD='postgres' - $ export DATABASE_PORT='5432' - $ export DATABASE_USER='postgres' - $ export DJANGO_SETTINGS_MODULE='home_industry.settings.local' - $ export SECRET_KEY='7&s33ax$lxxzti1)0y=8#tu!$7bdy)p$1@kn06tp&8x8i9#h2u' - ``` -- Migrate the database - ```bash - $ python3 manage.py migrate - ``` -- Set up API configuration by modifying `api_config.yaml` -- Create or update the API configuration in database - ```bash - $ python3 manage.py createorupdateapiconfig - ``` -- Create Superuser - ```bash - $ python3 manage.py createsuperuser - ``` -- Run server - ```bash - $ python3 manage.py runserver - ``` - -## Deployed API URLs - -- Staging: [https://industripilar-staging.herokuapp.com](https://industripilar-staging.herokuapp.com) -- Production: [https://api.industripilar.com](https://api.industripilar.com) - -## API Documentation - -[https://industripilar-staging.herokuapp.com/docs/](https://industripilar-staging.herokuapp.com/docs/) +# Home Industry API + +[![pipeline status](https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/sosial/pilar/pilar-backend/badges/dev/pipeline.svg)](https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/sosial/pilar/pilar-backend/-/commits/dev) +[![coverage report](https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/sosial/pilar/pilar-backend/badges/dev/coverage.svg)](https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/sosial/pilar/pilar-backend/-/commits/dev) +## Table of Contents + +- [Environment Variables](#environment-variables) + - [Local](#local) + - [CI](#ci) + - [Staging](#staging) + - [Production](#production) +- [Local Configuration](#local-configuration) +- [Deployed API URLs](#deployed-api-urls) +- [API Documentation](#api-documentation) + +## Environment Variables + +### Local + +Key | Required | Example +--- | --- | --- +`DATABASE_HOST` | yes | `127.0.0.1` +`DATABASE_NAME` | yes | `home_industry` +`DATABASE_PASSWORD` | yes | `postgres` +`DATABASE_PORT` | yes | `5432` +`DATABASE_USER` | yes | `postgres` +`DEBUG` | no | `True` +`DJANGO_SETTINGS_MODULE` | yes | `home_industry.settings.local` +`SECRET_KEY` | yes | `7&s33ax$lxxzti1)0y=8#tu!$7bdy)p$1@kn06tp&8x8i9#h2u` + +### CI + +Key | Required | Example +--- | --- | --- +`DJANGO_SETTINGS_MODULE` | yes | `home_industry.settings.ci` +`SECRET_KEY` | yes | `7&s33ax$lxxzti1)0y=8#tu!$7bdy)p$1@kn06tp&8x8i9#h2u` + +### Staging + +Key | Required | Example +--- | --- | --- +`AWS_ACCESS_KEY_ID` | yes | `AKIAIOSFODNN7EXAMPLE` +`AWS_REGION_NAME` | yes | `ap-southeast-1` +`AWS_SECRET_ACCESS_KEY` | yes | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` +`AWS_STORAGE_BUCKET_NAME` | yes | `homeindustry-api` +`DATABASE_URL` | yes | `postgres://postgres:postgres@ec2-117-21-174-214.compute-1.amazonaws.com:5432/home_industry` +`DEBUG` | no | `True` +`DJANGO_SETTINGS_MODULE` | yes | `home_industry.settings.staging` +`HOME_INDUSTRY_ADMIN_SITE_URL` | no | `https://homeindustry.com/admin/` +`HOME_INDUSTRY_ADMIN_SITE_USER_PATH` | no | `users/` +`HOME_INDUSTRY_ADMIN_SITE_PRODUCT_PATH` | no | `products/` +`HOME_INDUSTRY_ADMIN_SITE_TRANSACTION_PATH` | no | `transactions/` +`HOME_INDUSTRY_ADMIN_SITE_PROGRAM_PATH` | no | `programs/` +`HOME_INDUSTRY_ADMIN_SITE_PROGRAM_DONATION_PATH` | no | `program-donations/` +`SECRET_KEY` | yes | `7&s33ax$lxxzti1)0y=8#tu!$7bdy)p$1@kn06tp&8x8i9#h2u` + +### Production + +Key | Required | Example +--- | --- | --- +`AWS_ACCESS_KEY_ID` | yes | `AKIAIOSFODNN7EXAMPLE` +`AWS_REGION_NAME` | yes | `ap-southeast-1` +`AWS_SECRET_ACCESS_KEY` | yes | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` +`AWS_STORAGE_BUCKET_NAME` | yes | `homeindustry-api` +`DATABASE_HOST` | yes | `127.0.0.1` +`DATABASE_NAME` | yes | `home_industry` +`DATABASE_PASSWORD` | yes | `postgres` +`DATABASE_PORT` | yes | `5432` +`DATABASE_USER` | yes | `postgres` +`DJANGO_SETTINGS_MODULE` | yes | `home_industry.settings.production` +`HOME_INDUSTRY_ADMIN_SITE_URL` | no | `https://homeindustry.com/admin/` +`HOME_INDUSTRY_ADMIN_SITE_USER_PATH` | no | `users/` +`HOME_INDUSTRY_ADMIN_SITE_PRODUCT_PATH` | no | `products/` +`HOME_INDUSTRY_ADMIN_SITE_TRANSACTION_PATH` | no | `transactions/` +`HOME_INDUSTRY_ADMIN_SITE_PROGRAM_PATH` | no | `programs/` +`HOME_INDUSTRY_ADMIN_SITE_PROGRAM_DONATION_PATH` | no | `program-donations/` +`SECRET_KEY` | yes | `7&s33ax$lxxzti1)0y=8#tu!$7bdy)p$1@kn06tp&8x8i9#h2u` + +## Local Configuration + +- Create environment variables in root folder (change as needed) + + Filename: `.env` + ```bash + DATABASE_HOST=db + DATABASE_NAME=home_industry + DATABASE_PASSWORD=postgres + DATABASE_PORT=5432 + DATABASE_USER=postgres + DJANGO_SETTINGS_MODULE=home_industry.settings.local + SECRET_KEY=7&s33ax$lxxzti1)0y=8#tu!$7bdy)p$1@kn06tp&8x8i9#h2u + ALLOWED_HOST=0.0.0.0 + ``` +- Change `wait-for-it.sh` permission + ```bash + $ chmod +x wait-for-it.sh + ``` +- Run docker-compose + ```bash + $ sudo docker-compose up + ``` +- In another terminal, check running container + ```bash + $ docker ps + ``` +- Run collectstatic + ```bash + $ docker exec -it python manage.py collectstatic --noinput + ``` +- Create superuser + ```bash + $ docker exec -it python manage.py createsuperuser + ``` +- Create or update API configuration in database + ```bash + $ docker exec -it python manage.py createorupdateapiconfig + ``` +- Generate dummy data from seeders for database + ```bash + $ docker exec -it python manage.py createdummydata + ``` +- Access database + ```bash + $ docker exec -it psql -U postgres -d home_industry -h db + ``` + +## Deployed API URLs + +- Development: [https://pilar-be-dev.cs.ui.ac.id](https://pilar-be-dev.cs.ui.ac.id) +- Staging: [https://pilar-be-staging.cs.ui.ac.id](https://pilar-be-staging.cs.ui.ac.id) +- Production: [https://pilar-be.cs.ui.ac.id](https://pilar-be.cs.ui.ac.id) + +## API Documentation + +[https://pilar-be-dev.cs.ui.ac.id/docs/](https://pilar-be-dev.cs.ui.ac.id/docs/) diff --git a/api/management/commands/createdummydata.py b/api/management/commands/createdummydata.py new file mode 100644 index 0000000000000000000000000000000000000000..86785d76584ecf109ccfb07e8454a5667cc0f2dc --- /dev/null +++ b/api/management/commands/createdummydata.py @@ -0,0 +1,37 @@ +from django.core.management import base +from api import models +from api import seeds + +class Command(base.BaseCommand): + def handle(self, *args, **kwargs): + user, status = models.User.objects.get_or_create(**seeds.USER_DATA) + bank_account_transfer_destination, status = models.BankAccountTransferDestination.objects.get_or_create(**seeds.BANK_ACCOUNT_TRANSFER_DESTINATION) + + category, status = models.Category.objects.get_or_create(**seeds.CATEGORY_DATA) + subcategory, status = models.Subcategory.objects.get_or_create(**dict( + seeds.SUBCATEGORY_DATA, + category=category + )) + product, status = models.Product.objects.get_or_create(**dict(seeds.PRODUCT_DATA, subcategory=subcategory)) + transaction, status = models.Transaction.objects.get_or_create(**dict( + seeds.TRANSACTION_DATA, user=user + )) + transaction_item, status = models.TransactionItem.objects.get_or_create(**dict( + seeds.TRANSACTION_ITEM_DATA, + transaction=transaction, + product=product + )) + + program, status = models.Program.objects.get_or_create(**seeds.PROGRAM_DATA) + program_donation_pck, status = models.ProgramDonation.objects.get_or_create(**dict( + seeds.PROGRAM_DONATION_CASH_DATA, + user=user, + program=program + )) + program_donation_dlv, status = models.ProgramDonation.objects.get_or_create(**dict( + seeds.PROGRAM_DONATION_CASH_DATA, + user=user, + program=program + )) + + batch, status = models.Batch.objects.get_or_create(**seeds.BATCH_DATA) diff --git a/api/migrations/0003_product_unit.py b/api/migrations/0003_product_unit.py new file mode 100644 index 0000000000000000000000000000000000000000..2c9236546417e6fa7467bdbebbdcf5b78cbf42c6 --- /dev/null +++ b/api/migrations/0003_product_unit.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2021-03-27 05:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0002_auto_20201229_1028'), + ] + + operations = [ + migrations.AddField( + model_name='product', + name='unit', + field=models.CharField(default='buah', max_length=200, verbose_name='unit'), + ), + ] diff --git a/api/models.py b/api/models.py index 5ba7ff5c5b813554bcbe2055cdb5b1619fbfed23..982bcf8f6620c8d5d8a421ccea0b7d4448022c58 100644 --- a/api/models.py +++ b/api/models.py @@ -164,6 +164,7 @@ class Product(db_models.Model): validators=[validators.MinValueValidator(decimal.Decimal('0.01'))], verbose_name=_('total profit') ) + unit = db_models.CharField(default='buah', max_length=200, verbose_name=_('unit')) class Meta: ordering = ['subcategory', 'name', 'code', 'id'] verbose_name = _('product') diff --git a/api/seeds.py b/api/seeds.py index bcbb8fdba4f7031fef3a8c983a4c6e7a9885b221..3e5f762408b3cd8a8904f523b9b119d7c682ee60 100644 --- a/api/seeds.py +++ b/api/seeds.py @@ -35,7 +35,8 @@ PRODUCT_DATA = { 'description': 'Dummy description.', 'price': '2000', 'stock': 10, - 'modal':'1000', + 'modal': '1000', + 'unit': 'kg', } TRANSACTION_DATA = { diff --git a/api/serializers.py b/api/serializers.py index 90263e533b3c63ae75447df34490a1ecdbef1556..863a6b2d8db5a710f115bc836d6c4cf685b1a868 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -264,7 +264,8 @@ class ProductSerializer(serializers.ModelSerializer): 'modal', 'profit', 'image', - 'total_profit' + 'total_profit', + 'unit' ] model = models.Product read_only_fields = ['id', 'code'] diff --git a/api/tests.py b/api/tests.py index fb6530a0b5d1fd0bd8e3c904c17d39c2a80458b7..5525f1a4956eeb412effa84d1a3067ca0035bae1 100644 --- a/api/tests.py +++ b/api/tests.py @@ -1,4 +1,5 @@ import decimal +from os import name import tempfile from unittest import mock import datetime @@ -1169,6 +1170,7 @@ class ProductTest(rest_framework_test.APITestCase): def test_create_product_success(self): data = seeds.PRODUCT_DATA data['subcategory']= self.subcategory.id + data['unit'] = 'kg' response = request( 'POST', @@ -1180,10 +1182,12 @@ class ProductTest(rest_framework_test.APITestCase): self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(models.Product.objects.count(), 1) self.assertEqual(models.Product.objects.get(id=response.data['id']).name, data['name']) + self.assertEqual(models.Product.objects.get(id=response.data['id']).unit, data['unit']) def test_create_product_fail(self): data = dict(seeds.PRODUCT_DATA, subcategory=self.subcategory.id) data['name'] = None + data['unit'] = None response = request( 'POST', 'product-list', @@ -1200,7 +1204,8 @@ class ProductTest(rest_framework_test.APITestCase): data = { 'name': 'Dummy', 'price':'4000', - 'modal':'2000' + 'modal':'2000', + 'unit': 'gram' } response = request( 'PATCH', @@ -1212,6 +1217,7 @@ class ProductTest(rest_framework_test.APITestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(models.Product.objects.get(id=product.id).profit,2000) self.assertEqual(models.Product.objects.get(id=product.id).name, data['name']) + self.assertEqual(models.Product.objects.get(id=product.id).unit, data['unit']) data = dict(seeds.PRODUCT_DATA, subcategory=self.subcategory.id) response = request( 'PUT', @@ -1229,6 +1235,7 @@ class ProductTest(rest_framework_test.APITestCase): )) data = { 'name': '', + 'unit': '' } response = request( 'PATCH', @@ -1239,7 +1246,6 @@ class ProductTest(rest_framework_test.APITestCase): ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - class ShoppingCartTest(rest_framework_test.APITestCase): def setUp(self): self.superuser = models.User.objects.create_superuser(**seeds.SUPERUSER_DATA) @@ -1629,6 +1635,21 @@ class ProgramDonationTest(rest_framework_test.APITestCase): url_args=[program_donation.id] ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + def test_delete_by_program(self): + program = models.Program.objects.create(**seeds.PROGRAM_DATA ) + donation = models.ProgramDonation.objects.create(**dict( + seeds.PROGRAM_DONATION_CASH_DATA, + user=self.user, + program=program)) + response = request( + 'DELETE', + 'donation-by-program', + http_authorization=self.superuser_http_authorization, + url_args=[program.id] + ) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(models.ProgramDonation.objects.count(), 0) + class ChoicesViewsTest(rest_framework_test.APITestCase): diff --git a/api/urls.py b/api/urls.py index c47bffd1a9910b47cf21528a22c4d82b0e04726a..9feba438633d339bcf2440d842aad35de09da834 100644 --- a/api/urls.py +++ b/api/urls.py @@ -30,6 +30,7 @@ urlpatterns = [ name='cart-cancel-transaction' ), urls.path('donation/create/', api_views.DonationCreate.as_view(), name='donation-create'), + urls.path('donation/delete-by-p/', api_views.delete_donation_by_program, name='donation-by-program'), urls.path( 'donation/reupload-proof-of-bank-transfer/', api_views.DonationReuploadProofOfBankTransfer.as_view(), diff --git a/api/views.py b/api/views.py index dc62ed4007187378a1ea6ad3315ab042f08d1bff..7f0b05ba6d3deec665d2f60f438cc8e48a46928d 100644 --- a/api/views.py +++ b/api/views.py @@ -1,4 +1,3 @@ - from django import http, shortcuts from django.contrib import auth from django.db import transaction as db_transaction, utils as db_utils @@ -12,7 +11,8 @@ from rest_framework import ( permissions as rest_framework_permissions, response, status, views as rest_framework_views ) from rest_framework.authtoken import serializers as authtoken_serializers - +from rest_framework.decorators import api_view +from django.views.decorators.csrf import csrf_exempt from api import ( constants, exceptions as api_exceptions, filters as api_filters, models, paginations, permissions as api_permissions, reports_writer, schemas, serializers as api_serializers, @@ -626,6 +626,11 @@ class ProductList(generics.ListCreateAPIView): def post(self, request, _format=None): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) + validated_image = None + try: + validated_image = serializer.validated_data['image'] + except KeyError: + pass product = models.Product.objects.create( name=serializer.validated_data['name'], description=serializer.validated_data['description'], @@ -633,7 +638,9 @@ class ProductList(generics.ListCreateAPIView): stock=serializer.validated_data['stock'], modal=serializer.validated_data['modal'], subcategory=models.Subcategory.objects.get(name=serializer.validated_data['subcategory']), - total_profit=0 + total_profit=0, + unit=serializer.validated_data['unit'], + image=validated_image ) product.profit= (product.price - product.modal) product.save() @@ -643,6 +650,7 @@ class ProductList(generics.ListCreateAPIView): ) + class ProductDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = [ api_permissions.IsAdminUserOrReadOnly, @@ -836,6 +844,14 @@ class ProgramDonationList(generics.ListAPIView): return queryset.filter(user=self.request.user) return queryset +@api_view(['DELETE']) +@csrf_exempt +def delete_donation_by_program(request, pid): + if ((request.user) and (request.user.is_staff)): + program = models.Program.objects.get(id=pid) + donation = models.ProgramDonation.objects.filter(program=program) + donation.delete() + return response.Response(status=status.HTTP_204_NO_CONTENT) class ProgramDonationListCSH(generics.ListAPIView): filter_backends = [ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..9a9ad435a5a0a84d7d67c4b85d39b84c12372a3b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: "3.9" + +services: + db: + image: postgres:13 + environment: + - POSTGRES_DB=home_industry + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_HOST=127.0.0.1 + web: + build: . + command: bash -c " + python manage.py makemigrations && + python manage.py migrate && + /wait-for-it.sh db:5432 -- python manage.py runserver 0.0.0.0:8000" + volumes: + - ./wait-for-it.sh:/wait-for-it.sh + - .:/code + ports: + - "8000:8000" + depends_on: + - db + env_file: .env \ No newline at end of file diff --git a/wait-for-it.sh b/wait-for-it.sh new file mode 100755 index 0000000000000000000000000000000000000000..8bb4cf008b0f9036963bcbe732bb07a8f3f99fbc --- /dev/null +++ b/wait-for-it.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi +'+