From 0b668ac26a6dd2a1a8c269e470d5f832e3966661 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 23 Oct 2023 16:27:06 +0300 Subject: [PATCH 001/128] Set up project with django and install drf --- .gitignore | 1 + Pipfile | 12 ++++ Pipfile.lock | 54 ++++++++++++++++ db.sqlite3 | 0 manage.py | 22 +++++++ post_it_backend/__init__.py | 0 post_it_backend/asgi.py | 16 +++++ post_it_backend/settings.py | 123 ++++++++++++++++++++++++++++++++++++ post_it_backend/urls.py | 22 +++++++ post_it_backend/wsgi.py | 16 +++++ requirements.txt | 2 + 11 files changed, 268 insertions(+) create mode 100644 .gitignore create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 db.sqlite3 create mode 100755 manage.py create mode 100644 post_it_backend/__init__.py create mode 100644 post_it_backend/asgi.py create mode 100644 post_it_backend/settings.py create mode 100644 post_it_backend/urls.py create mode 100644 post_it_backend/wsgi.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba0430d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..8a20dc9 --- /dev/null +++ b/Pipfile @@ -0,0 +1,12 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +django = "*" + +[dev-packages] + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..a7d5466 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,54 @@ +{ + "_meta": { + "hash": { + "sha256": "627ef89f247ecee27e9ef0dabe116108d09c47abf171c900a8817befa64f9dd2" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "asgiref": { + "hashes": [ + "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", + "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed" + ], + "markers": "python_version >= '3.7'", + "version": "==3.7.2" + }, + "django": { + "hashes": [ + "sha256:08f41f468b63335aea0d904c5729e0250300f6a1907bf293a65499496cdbc68f", + "sha256:a64d2487cdb00ad7461434320ccc38e60af9c404773a2f95ab0093b4453a3215" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==4.2.6" + }, + "sqlparse": { + "hashes": [ + "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3", + "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c" + ], + "markers": "python_version >= '3.5'", + "version": "==0.4.4" + }, + "typing-extensions": { + "hashes": [ + "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", + "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" + ], + "markers": "python_version < '3.11'", + "version": "==4.8.0" + } + }, + "develop": {} +} diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000..e69de29 diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..94360fe --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/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', 'post_it_backend.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() diff --git a/post_it_backend/__init__.py b/post_it_backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/post_it_backend/asgi.py b/post_it_backend/asgi.py new file mode 100644 index 0000000..29fce65 --- /dev/null +++ b/post_it_backend/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for post_it_backend project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'post_it_backend.settings') + +application = get_asgi_application() diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py new file mode 100644 index 0000000..8822879 --- /dev/null +++ b/post_it_backend/settings.py @@ -0,0 +1,123 @@ +""" +Django settings for post_it_backend project. + +Generated by 'django-admin startproject' using Django 4.2.6. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.2/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-!l5_lbaz2+e6)&haqh@caqe_ixlr5oukfxuufqw+h3=5haztej' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'post_it_backend.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'post_it_backend.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.2/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/post_it_backend/urls.py b/post_it_backend/urls.py new file mode 100644 index 0000000..e5cd2b7 --- /dev/null +++ b/post_it_backend/urls.py @@ -0,0 +1,22 @@ +""" +URL configuration for post_it_backend project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/post_it_backend/wsgi.py b/post_it_backend/wsgi.py new file mode 100644 index 0000000..b0d9ada --- /dev/null +++ b/post_it_backend/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for post_it_backend project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'post_it_backend.settings') + +application = get_wsgi_application() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d80bd13 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +django +djangorestframework \ No newline at end of file From 3b6e2e46bb2418a3460e53e5693f9bec366678ad Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 24 Oct 2023 09:36:34 +0300 Subject: [PATCH 002/128] Create user model --- db.sqlite3 | Bin 0 -> 143360 bytes post_it_backend/settings.py | 1 + user/__init__.py | 0 user/admin.py | 3 +++ user/apps.py | 6 ++++++ user/migrations/0001_initial.py | 23 +++++++++++++++++++++++ user/migrations/__init__.py | 0 user/models.py | 7 +++++++ user/tests.py | 3 +++ user/views.py | 3 +++ 10 files changed, 46 insertions(+) create mode 100644 user/__init__.py create mode 100644 user/admin.py create mode 100644 user/apps.py create mode 100644 user/migrations/0001_initial.py create mode 100644 user/migrations/__init__.py create mode 100644 user/models.py create mode 100644 user/tests.py create mode 100644 user/views.py diff --git a/db.sqlite3 b/db.sqlite3 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..458273a5f61609062be4cd075c2cb8f0382ee3a4 100644 GIT binary patch literal 143360 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 literal 0 HcmV?d00001 diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py index 8822879..70213ee 100644 --- a/post_it_backend/settings.py +++ b/post_it_backend/settings.py @@ -37,6 +37,7 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'user', ] MIDDLEWARE = [ diff --git a/user/__init__.py b/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/admin.py b/user/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/user/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/user/apps.py b/user/apps.py new file mode 100644 index 0000000..36cce4c --- /dev/null +++ b/user/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user' diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py new file mode 100644 index 0000000..fdb533e --- /dev/null +++ b/user/migrations/0001_initial.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.6 on 2023-10-24 06:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('username', models.CharField(max_length=50, unique=True)), + ('password', models.CharField(max_length=128)), + ('email', models.EmailField(max_length=100, unique=True)), + ], + ), + ] diff --git a/user/migrations/__init__.py b/user/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/models.py b/user/models.py new file mode 100644 index 0000000..ab1f454 --- /dev/null +++ b/user/models.py @@ -0,0 +1,7 @@ +from django.db import models + +# Create your models here. +class User(models.Model): + username=models.CharField(max_length=50, unique=True) + password=models.CharField(max_length=128) + email=models.EmailField(max_length=100, unique=True) \ No newline at end of file diff --git a/user/tests.py b/user/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/user/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/user/views.py b/user/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/user/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From e8391bcbc9324fd92f6ded46a42ab01a1e880b0a Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 24 Oct 2023 09:52:53 +0300 Subject: [PATCH 003/128] Register user model in admin --- db.sqlite3 | Bin 143360 -> 143360 bytes user/admin.py | 4 +++- user/models.py | 5 ++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/db.sqlite3 b/db.sqlite3 index 458273a5f61609062be4cd075c2cb8f0382ee3a4..201d75be7b6dc687000f895e0d9061f92218f8fc 100644 GIT binary patch delta 501 zcmaiwJxBs!7{}j_v-8d3Nv$qO@N%&@{J1+)aVlJ*y(vO3Pef5q`Vc}Sp|PcKYi?`_ z!Ge|o*R~cnYm;ykhZ;IhNSa#S2j2hh{k?ylM$g{p*{@cy|Jswp{`>T;*Tq8`K_Ala z1XZ|%8@Ow&iMz18NN51-1+APjQkq`Mms3R}N1D};vI1|2n|DwzK0VGJrN##lC{=Ou zWzIdrBZQXNMK|=&4PmKbCon|t1;duJ(FegsG3E(JG2>@VkiZDRH;l~i6BtJsCX_PP ze#YE_`Lc?}ty<1X0nU?zA&{7a*|xH0ekmG2?TTvs;sYd%fav$`)K*6Q^GUI{B| zKn@3#h!~D0BJo664#uNuEEY@RUy1xjBL63G#1fHcFrupQ&1k!aInk`wLGS?`v)&`@ zc!&)NZb_@;&kR{nt+|yeY58I>W0XADN(O>i5M%9f8OXP*v_{$jhU-Dn!-_2jf-_1XZzhbjsK?48eGJPWn z1_lOUj>OWEjQE1oqTI~l;>>(;W@cl?$p?9a+1Q1dm~}atZ|ZNqsn4igu$kq-ANffW YSeCOKU=#py*afz8KVbYR4-(f00QMv&%m4rY diff --git a/user/admin.py b/user/admin.py index 8c38f3f..779867f 100644 --- a/user/admin.py +++ b/user/admin.py @@ -1,3 +1,5 @@ from django.contrib import admin - +from .models import User # Register your models here. + +admin.site.register(User) \ No newline at end of file diff --git a/user/models.py b/user/models.py index ab1f454..18eb6fd 100644 --- a/user/models.py +++ b/user/models.py @@ -4,4 +4,7 @@ class User(models.Model): username=models.CharField(max_length=50, unique=True) password=models.CharField(max_length=128) - email=models.EmailField(max_length=100, unique=True) \ No newline at end of file + email=models.EmailField(max_length=100, unique=True) + + def __str__(self): + return self.username \ No newline at end of file From a6328049fd65b3bad47ccba63a4242ab3b9c4581 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 24 Oct 2023 11:34:55 +0300 Subject: [PATCH 004/128] Create user signup api endpoint --- db.sqlite3 | Bin 143360 -> 143360 bytes post_it_backend/settings.py | 1 + post_it_backend/urls.py | 3 ++- user/serializers.py | 7 +++++++ user/urls.py | 6 ++++++ user/views.py | 13 ++++++++++++- 6 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 user/serializers.py create mode 100644 user/urls.py diff --git a/db.sqlite3 b/db.sqlite3 index 201d75be7b6dc687000f895e0d9061f92218f8fc..ff0b73ebba967f8d76e136b90ff8cde205c98c69 100644 GIT binary patch delta 438 zcmZp8z|ru4V}dlJ&O{k!MxBibQTlA`;>^s(jFS)Y2sdBW-+o!2(eQ#0JHHkK|0Dh* z{7d-T__O%E__a1GDv0pc%dxXENDAu~m*%A;RvH=^Ll_R}xrv!Mddc~@a%^Dv#JtS3 z#0;Q3gn^KkVr69z71m5kOiKjHf+&QX3=1oRxUhC&R%%`*NGpg&$jUJTO_mnc%`d7< z%>`+OFc1o+AK_4$!p_Zt1&jGjxR@mwA&zASF(K|^<6stL z1UZ70lUbY*Y#|Fs7GgB>^tbX%;y@FN8TdEwcktijp9?hg2!Aoq#900`5oTSei3m5a lg9Oq2%qGOF3AP2Hj#Zdh8)6?qj0L0@-BZldZ|XCt0{~Q%f*b$< delta 96 zcmZp8z|ru4V}dlJ`a~ILM)i#eQTl95;>^s(jFS)Y2sdBW-+o!2(eMHX6aNtg{zv>r yHVYQ);GcekpUD9z^p=7DE&q3*&_n*|Z{?Z9fkHPK_;2#R1qz+zpMF!HNgV*;(jY(p diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py index 70213ee..a0df90c 100644 --- a/post_it_backend/settings.py +++ b/post_it_backend/settings.py @@ -38,6 +38,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'user', + 'rest_framework', ] MIDDLEWARE = [ diff --git a/post_it_backend/urls.py b/post_it_backend/urls.py index e5cd2b7..84778ea 100644 --- a/post_it_backend/urls.py +++ b/post_it_backend/urls.py @@ -15,8 +15,9 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), + path('api/', include('user.urls')), ] diff --git a/user/serializers.py b/user/serializers.py new file mode 100644 index 0000000..eca4a45 --- /dev/null +++ b/user/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from .models import User + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['id', 'username', 'password', 'email'] \ No newline at end of file diff --git a/user/urls.py b/user/urls.py new file mode 100644 index 0000000..9fb3902 --- /dev/null +++ b/user/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('user/signup/', views.create_user), +] \ No newline at end of file diff --git a/user/views.py b/user/views.py index 91ea44a..ff9c616 100644 --- a/user/views.py +++ b/user/views.py @@ -1,3 +1,14 @@ from django.shortcuts import render - +from .serializers import UserSerializer +from rest_framework.decorators import api_view +from rest_framework.response import Response +from rest_framework import status # Create your views here. + +@api_view(['POST']) +def create_user(request): + if request.method == 'POST': + serializer = UserSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) From b3f0bb263b02780faa9506c626675d81866c4146 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 24 Oct 2023 12:36:08 +0300 Subject: [PATCH 005/128] Move routes inside api urls --- api/__init__.py | 0 api/admin.py | 3 +++ api/apps.py | 6 ++++++ api/migrations/__init__.py | 0 api/models.py | 3 +++ api/tests.py | 3 +++ api/urls.py | 5 +++++ api/views.py | 3 +++ db.sqlite3 | Bin 143360 -> 143360 bytes post_it_backend/settings.py | 1 + post_it_backend/urls.py | 2 +- user/urls.py | 2 +- 12 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 api/__init__.py create mode 100644 api/admin.py create mode 100644 api/apps.py create mode 100644 api/migrations/__init__.py create mode 100644 api/models.py create mode 100644 api/tests.py create mode 100644 api/urls.py create mode 100644 api/views.py diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/admin.py b/api/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/api/apps.py b/api/apps.py new file mode 100644 index 0000000..66656fd --- /dev/null +++ b/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'api' diff --git a/api/migrations/__init__.py b/api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/models.py b/api/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/api/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/api/tests.py b/api/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/api/urls.py b/api/urls.py new file mode 100644 index 0000000..8eb5f2f --- /dev/null +++ b/api/urls.py @@ -0,0 +1,5 @@ +from django.urls import path, include + +urlpatterns = [ + path('user/', include('user.urls')) +] \ No newline at end of file diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/api/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/db.sqlite3 b/db.sqlite3 index ff0b73ebba967f8d76e136b90ff8cde205c98c69..ddd0097b0e85cb892ea4ffc6f636f3b6ce958790 100644 GIT binary patch delta 177 zcmZp8z|ru4V}dlJ?nD`9M%|4GQTl8g;>^s(jFS)Y2sdBW-+o!2(eQ!@2R|PJ|0Dh* z{7d-T__O%E__g@?HY+N8$)=Ww*E-^GR22l>_xrv!Mddc~@)3x}SZt-yN zZ(!iR0o41Jf5Ub@0j34~oE*%ej3A{P(|5``4>Fz|2S@8G}5pRk=zfhmArScq8@ OtO8*e$Mj--rd|MzYceSS delta 118 zcmZp8z|ru4V}dlJ&O{k!MxBibQTlA`;>^s(jFS)Y2sdBW-+o!2(eQ#0JHHkK|0Dh* z{7d-T__O%E__a1GDv0n;*Wzco#l_CQlY##RQ1b0|0Rg53{1XGXr|* Date: Tue, 24 Oct 2023 15:27:47 +0300 Subject: [PATCH 006/128] Hash user password --- db.sqlite3 | Bin 143360 -> 143360 bytes user/migrations/0001_initial.py | 2 +- user/serializers.py | 11 ++++++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/db.sqlite3 b/db.sqlite3 index ddd0097b0e85cb892ea4ffc6f636f3b6ce958790..cf8a4c302f126e44608d31a63f4c24a36a28591a 100644 GIT binary patch delta 1441 zcmaiyOKcle6o$tcH%V!dX%vMZ@^A`)rbM>qacAy~!GNE!$MyKt@k^j|JRUoKWXA8Y zvq=MDhfvE%tSV7fpspZUAi)M!bO#HBpkfg=L2OW$EI`6`Bs^WQxzhb~&VSDT-+Sk5 z_RiVt-Bru>y)~a@`-|JZJ&zHjizHoubWkh~Ioyb^zC2~kl#!MK^vJ}S}={1*$L4su%n28V04EHv4or+> z7@9Klb=u4T!BWQ8>!w|39I^yw{IhP_1#k?&8N<6_@&Q=+sdS8Xq*PL@saHX6Jt4oB8~5CuS|4? zsgVRZk9~&BU_Au;1N-rGZu1s)@4>T1&q+RqAc%GT3Tm@-+e&M1bf7CJN?D9==a!~t zmZnhq?CCGg`)?ePjR2>2@_Am331u;!pn%9hdCRK_LXZfR)AfD_I`jEph^K{Qsgr5J zQWwS&AxQ|gnSCj&%5D-TDLaANNw7Jx$+-aMpczPT%-G-fc(Z&URq75|J9xT!Zdgu1 z2g?mD;wO#iP3vQ1;qmFU%^wzy-g(h-Df+JHDeO}^-P5$L3>y1dsU&k0Y*zK2UXp>@ z@6;R$-%nSgfrvivB$DyHpxh5;lbKk&SCR|)kVpv`B3X&n8k}G+Bs{PqW&FwkR}b_< zVnbGNwhz=mI_Cp>p&ZYGUdy9Yc&>O9tQFKuzA;F|lX(UoQ0;1|!^Jqey`1yyL9!f9 zkx7rJtzS=)RtWo~jm49q!U%8J05PNHM99agRS&Y4?iR3ae*hVZYpV5zXGN z(05gITn@qLtz<}I>`dxrPm+7Qm>0c~=3#U@SSl&u2HrXtbT}q)ksD#hp($mEQ#5h% z;0)XRzmQHow>~+wp8ak6@UHb!>o=CG!^_OoFRz?g&TZ?HXB!L74}TB3@DRa1z%~{h z4j<$G!h8#zMplm9J5r-E(2U#K^4kL3EehEv5$Hy8T0fA!35y^=ZKdm#q(?8*@kmEV zYu>CT0%v;QhjLfV#t0xfo4!Fk<?o}H+BC^gY< zSf-KXWA}}MRFFmk_JTf=i~u1HTal`yG_xT!E)}|4$!rfVN9vVYA5RI4&`={`LP_)L zAR7-I(tMBy)s{d8V60qokF+Keo*5;Fog>&e_80c#bg}U>wtX30v5bwPGw70KxX{!L sx_o)mcTxQa!5(3MV!sZnzrxrhbmyXaB8o1e*GGL5GPQ`_7%NQBzYiy%bpQYW delta 1791 zcmah}OKcle6rJ(dvE3$lq-h9EoWzNskVbZ9-u%sXY??YBC$ZDijS))59^3Kw?}?o{ zRpkl-_x(IAgg^*xTvH&FBjHP;gI0?F&Yd0y#Yg2-{Kpfh8P z1rt~niu&#tYsF}iA@#dsmRbak6L?DhZ_H8)a-~R84~|><5Ew{tqW*pt25F8L^qg3=BUVu=aoM1*D>%#L4&C(>{z=ofbWTluZ>0e9?TYOla5lH=`i75-lu#l$p?0xg<2v=Dha93$g%NNvYMo{XO+lWUQ*_F zcIAy?VUeoxYn&WxEX%XAYrD(qYmJRn7`j@yqSX2F%aDY$k0O1L@fxl30Vwzx9*PuW z7G)lp`yIM4usIJB~a*O$N zY)>kyNp)^MIUl{=^f6qPSkkF^$fq7+L({FRik_)Z`GKC0(tQHG4MV~qJw?=pDr7Cgn@s+qr-vQ55cg5z;UO| zPT+3m88iu9pP<)oTU?RZ~+({J3Ik`*PbOa;6*Z{2#++5Op!5FjbPz|m+ogz(2fsIDN?lY*S( z0}SV9&=0cQon!p3iRUd-XDHH#+Hrw?4Ezd?z?a}%pn?T(3G}0>`n$h1;&Na^gVV{h zwnIVMFrC>PcRH{OgFdZZQsgFL+ZPMpWAbt-myx#--@bJC5r-K&BWGm98m7Z}>}U-P z&a`ATUCJ50hV9VfXiK2fJOOE=p&Vy0bm$NPVLY4D5y diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py index fdb533e..54aa463 100644 --- a/user/migrations/0001_initial.py +++ b/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.6 on 2023-10-24 06:34 +# Generated by Django 4.2.6 on 2023-10-24 12:22 from django.db import migrations, models diff --git a/user/serializers.py b/user/serializers.py index eca4a45..8b7b868 100644 --- a/user/serializers.py +++ b/user/serializers.py @@ -1,7 +1,16 @@ from rest_framework import serializers from .models import User +from django.contrib.auth.hashers import make_password class UserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ['id', 'username', 'password', 'email'] \ No newline at end of file + fields = ['id', 'username', 'password', 'email'] + extra_kwargs = {'password': {'write_only': True}} + + def create(self, validated_data): + password = validated_data.pop('password') + user = User(**validated_data) + user.password = make_password(password) + user.save() + return user \ No newline at end of file From 04e39de6ebd175f525751d4bc4768dafcfa5eaf2 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 24 Oct 2023 16:30:49 +0300 Subject: [PATCH 007/128] Re-arrange installed apps --- post_it_backend/settings.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py index 16b7266..fbc7798 100644 --- a/post_it_backend/settings.py +++ b/post_it_backend/settings.py @@ -31,15 +31,18 @@ # Application definition INSTALLED_APPS = [ + # My apps + 'user', + 'api', + + # Django apps + 'rest_framework', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'user', - 'rest_framework', - 'api', ] MIDDLEWARE = [ From 86d996eff2d1ddcc4646f929a6959cf739f4bd7f Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 26 Oct 2023 16:04:50 +0300 Subject: [PATCH 008/128] Create user sign in api --- db.sqlite3 | Bin 143360 -> 147456 bytes post_it_backend/settings.py | 12 ++++++++++++ user/admin.py | 4 ++-- user/migrations/0001_initial.py | 16 +++++++++++++--- user/models.py | 33 ++++++++++++++++++++++++++++---- user/serializers.py | 6 +++--- user/urls.py | 1 + user/views.py | 19 ++++++++++++++++++ 8 files changed, 79 insertions(+), 12 deletions(-) diff --git a/db.sqlite3 b/db.sqlite3 index cf8a4c302f126e44608d31a63f4c24a36a28591a..efa6d68a5f4768ae0544cac7c22dd25918e394b8 100644 GIT binary patch literal 147456 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
wjP2XgVEAWnGe-I8>Y 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 diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py index fbc7798..59305e2 100644 --- a/post_it_backend/settings.py +++ b/post_it_backend/settings.py @@ -36,6 +36,7 @@ 'api', # Django apps + 'rest_framework.authtoken', 'rest_framework', 'django.contrib.admin', 'django.contrib.auth', @@ -45,6 +46,17 @@ 'django.contrib.staticfiles', ] +AUTH_USER_MODEL = "user.CustomUser" + +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.AllowAny' + ], + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.TokenAuthentication', + ] +} + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/user/admin.py b/user/admin.py index 779867f..fc0525d 100644 --- a/user/admin.py +++ b/user/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import User +from .models import CustomUser # Register your models here. -admin.site.register(User) \ No newline at end of file +admin.site.register(CustomUser) \ No newline at end of file diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py index 54aa463..5e855e2 100644 --- a/user/migrations/0001_initial.py +++ b/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.6 on 2023-10-24 12:22 +# Generated by Django 4.2.6 on 2023-10-26 12:32 from django.db import migrations, models @@ -8,16 +8,26 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ migrations.CreateModel( - name='User', + name='CustomUser', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), ('username', models.CharField(max_length=50, unique=True)), - ('password', models.CharField(max_length=128)), ('email', models.EmailField(max_length=100, unique=True)), + ('is_active', models.BooleanField(default=True)), + ('is_staff', models.BooleanField(default=False)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), ], + options={ + 'abstract': False, + }, ), ] diff --git a/user/models.py b/user/models.py index 18eb6fd..ec668dc 100644 --- a/user/models.py +++ b/user/models.py @@ -1,10 +1,35 @@ from django.db import models +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin + +class CustomUserManager(BaseUserManager): + def create_user(self, username, email, password, **extra_fields): + if not email: + raise ValueError('Users must have an email address') + email = self.normalize_email(email) + user = self.model(username=username, email=email, **extra_fields) + user.set_password(password) + user.save() + return user + + def create_superuser(self, username, email, password): + user = self.create_user(username, email, password) + user.is_superuser = True + user.is_staff = True + user.is_active = True + user.save() + return user # Create your models here. -class User(models.Model): - username=models.CharField(max_length=50, unique=True) - password=models.CharField(max_length=128) - email=models.EmailField(max_length=100, unique=True) +class CustomUser(AbstractBaseUser, PermissionsMixin): + username = models.CharField(max_length=50, unique=True) + email = models.EmailField(max_length=100, unique=True) + is_active = models.BooleanField(default=True) + is_staff = models.BooleanField(default=False) + + objects = CustomUserManager() + + USERNAME_FIELD = 'username' + REQUIRED_FIELDS = ['email'] def __str__(self): return self.username \ No newline at end of file diff --git a/user/serializers.py b/user/serializers.py index 8b7b868..d519713 100644 --- a/user/serializers.py +++ b/user/serializers.py @@ -1,16 +1,16 @@ from rest_framework import serializers -from .models import User +from .models import CustomUser from django.contrib.auth.hashers import make_password class UserSerializer(serializers.ModelSerializer): class Meta: - model = User + model = CustomUser fields = ['id', 'username', 'password', 'email'] extra_kwargs = {'password': {'write_only': True}} def create(self, validated_data): password = validated_data.pop('password') - user = User(**validated_data) + user = CustomUser(**validated_data) user.password = make_password(password) user.save() return user \ No newline at end of file diff --git a/user/urls.py b/user/urls.py index c9011b9..6e6b636 100644 --- a/user/urls.py +++ b/user/urls.py @@ -3,4 +3,5 @@ urlpatterns = [ path('signup/', views.create_user), + path('signin/', views.signin_user), ] \ No newline at end of file diff --git a/user/views.py b/user/views.py index ff9c616..23948ca 100644 --- a/user/views.py +++ b/user/views.py @@ -3,6 +3,8 @@ from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework import status +from rest_framework.authtoken.models import Token +from django.contrib.auth import authenticate # Create your views here. @api_view(['POST']) @@ -12,3 +14,20 @@ def create_user(request): if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) + +@api_view(['POST']) +def signin_user(request): + if request.method == 'POST': + username = request.data.get('username') + password = request.data.get('password') + + user = authenticate(username=username, password=password) + if not user: + return Response({ + 'error': 'username or password is incorrect'}, + status=status.HTTP_401_UNAUTHORIZED) + token, _ = Token.objects.get_or_create(user=user) + return Response({ + 'token': token.key}, + status=status.HTTP_200_OK) + From c654177a5e7726169f65438cd2166dd32051f015 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 27 Oct 2023 10:54:14 +0300 Subject: [PATCH 009/128] Set up postgresql database --- .gitignore | 5 ++++- db.sqlite3 | Bin 147456 -> 0 bytes post_it_backend/settings.py | 19 +++++++++++++------ requirements.txt | 4 +++- 4 files changed, 20 insertions(+), 8 deletions(-) delete mode 100644 db.sqlite3 diff --git a/.gitignore b/.gitignore index ba0430d..2301537 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -__pycache__/ \ No newline at end of file +__pycache__/ +.env +env/ +db.sqlite3 \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 deleted file mode 100644 index efa6d68a5f4768ae0544cac7c22dd25918e394b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 147456 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 z Date: Fri, 27 Oct 2023 16:29:52 +0300 Subject: [PATCH 010/128] Remove custom user model implementation --- post_it_backend/settings.py | 2 -- user/admin.py | 5 ----- user/migrations/0001_initial.py | 33 -------------------------------- user/migrations/__init__.py | 0 user/models.py | 34 +-------------------------------- user/serializers.py | 6 +++--- 6 files changed, 4 insertions(+), 76 deletions(-) delete mode 100644 user/admin.py delete mode 100644 user/migrations/0001_initial.py delete mode 100644 user/migrations/__init__.py diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py index d0addae..ff43747 100644 --- a/post_it_backend/settings.py +++ b/post_it_backend/settings.py @@ -49,8 +49,6 @@ 'django.contrib.staticfiles', ] -AUTH_USER_MODEL = "user.CustomUser" - REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.AllowAny' diff --git a/user/admin.py b/user/admin.py deleted file mode 100644 index fc0525d..0000000 --- a/user/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib import admin -from .models import CustomUser -# Register your models here. - -admin.site.register(CustomUser) \ No newline at end of file diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py deleted file mode 100644 index 5e855e2..0000000 --- a/user/migrations/0001_initial.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 4.2.6 on 2023-10-26 12:32 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='CustomUser', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(max_length=50, unique=True)), - ('email', models.EmailField(max_length=100, unique=True)), - ('is_active', models.BooleanField(default=True)), - ('is_staff', models.BooleanField(default=False)), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/user/migrations/__init__.py b/user/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/user/models.py b/user/models.py index ec668dc..d49766e 100644 --- a/user/models.py +++ b/user/models.py @@ -1,35 +1,3 @@ from django.db import models -from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin -class CustomUserManager(BaseUserManager): - def create_user(self, username, email, password, **extra_fields): - if not email: - raise ValueError('Users must have an email address') - email = self.normalize_email(email) - user = self.model(username=username, email=email, **extra_fields) - user.set_password(password) - user.save() - return user - - def create_superuser(self, username, email, password): - user = self.create_user(username, email, password) - user.is_superuser = True - user.is_staff = True - user.is_active = True - user.save() - return user - -# Create your models here. -class CustomUser(AbstractBaseUser, PermissionsMixin): - username = models.CharField(max_length=50, unique=True) - email = models.EmailField(max_length=100, unique=True) - is_active = models.BooleanField(default=True) - is_staff = models.BooleanField(default=False) - - objects = CustomUserManager() - - USERNAME_FIELD = 'username' - REQUIRED_FIELDS = ['email'] - - def __str__(self): - return self.username \ No newline at end of file +# Create your models here. \ No newline at end of file diff --git a/user/serializers.py b/user/serializers.py index d519713..b520046 100644 --- a/user/serializers.py +++ b/user/serializers.py @@ -1,16 +1,16 @@ from rest_framework import serializers -from .models import CustomUser +from django.contrib.auth.models import User from django.contrib.auth.hashers import make_password class UserSerializer(serializers.ModelSerializer): class Meta: - model = CustomUser + model = User fields = ['id', 'username', 'password', 'email'] extra_kwargs = {'password': {'write_only': True}} def create(self, validated_data): password = validated_data.pop('password') - user = CustomUser(**validated_data) + user = User(**validated_data) user.password = make_password(password) user.save() return user \ No newline at end of file From 33f0d530f232082312246dcf77cdee1ac52aa85a Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 30 Oct 2023 09:33:33 +0300 Subject: [PATCH 011/128] Add comma after group app --- post_it_backend/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py index a7ba210..4d681ee 100644 --- a/post_it_backend/settings.py +++ b/post_it_backend/settings.py @@ -37,7 +37,7 @@ # My apps 'user', 'api', - 'group' + 'group', # Django apps 'rest_framework.authtoken', From f3cb384343e70d91fce07d054c5ad62c31f2fc78 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 30 Oct 2023 15:13:13 +0300 Subject: [PATCH 012/128] Create broadcast groups --- api/urls.py | 3 ++- group/migrations/0001_initial.py | 26 ++++++++++++++++++++++++++ group/models.py | 10 ++++++++++ group/serializers.py | 7 +++++++ group/urls.py | 6 ++++++ group/views.py | 18 ++++++++++++++++++ 6 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 group/migrations/0001_initial.py create mode 100644 group/models.py create mode 100644 group/serializers.py create mode 100644 group/urls.py diff --git a/api/urls.py b/api/urls.py index 8eb5f2f..b0b5646 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,5 +1,6 @@ from django.urls import path, include urlpatterns = [ - path('user/', include('user.urls')) + path('user/', include('user.urls')), + path('group/', include('group.urls')), ] \ No newline at end of file diff --git a/group/migrations/0001_initial.py b/group/migrations/0001_initial.py new file mode 100644 index 0000000..a84afd8 --- /dev/null +++ b/group/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.6 on 2023-10-30 08:26 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Group', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, unique=True)), + ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups_created', to=settings.AUTH_USER_MODEL)), + ('members', models.ManyToManyField(related_name='groups_joined', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/group/models.py b/group/models.py new file mode 100644 index 0000000..8c2376b --- /dev/null +++ b/group/models.py @@ -0,0 +1,10 @@ +from django.db import models +from django.contrib.auth.models import User + +class Group(models.Model): + name=models.CharField(max_length=255, unique=True) + members=models.ManyToManyField(User, related_name='groups_joined') + creator=models.ForeignKey(User, on_delete=models.CASCADE, related_name='groups_created') + + def __str__(self): + return self.name \ No newline at end of file diff --git a/group/serializers.py b/group/serializers.py new file mode 100644 index 0000000..3ba22a3 --- /dev/null +++ b/group/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from .models import Group + +class GroupSerializer(serializers.ModelSerializer): + class Meta: + model = Group + fields = ['id', 'name', 'members', 'creator'] \ No newline at end of file diff --git a/group/urls.py b/group/urls.py new file mode 100644 index 0000000..01fae8d --- /dev/null +++ b/group/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.create_group) +] \ No newline at end of file diff --git a/group/views.py b/group/views.py index 91ea44a..0dfee21 100644 --- a/group/views.py +++ b/group/views.py @@ -1,3 +1,21 @@ from django.shortcuts import render +from rest_framework.decorators import api_view, permission_classes +from rest_framework.response import Response +from rest_framework import status +from .serializers import GroupSerializer +from rest_framework.permissions import IsAuthenticated # Create your views here. + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def create_group(request): + if request.method == 'POST': + data = request.data.copy() + data['creator']=request.user.id + serializer = GroupSerializer(data=data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file From 57f33c97876c4d1aec8fed31093ccedbdf085671 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 31 Oct 2023 10:25:09 +0300 Subject: [PATCH 013/128] Update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9ac3357..186d4a8 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ # post-it-backend + +PostIt ​is a simple application that allows friends and colleagues to create groups for notifications. This way one person can post notifications to everyone by sending a message once. The application allows people to create accounts, create groups add registered users to the groups, and then send messages to these groups whenever they want. From 4ea5780a1f4759b50f579a3d5814084b38b7c5a6 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 31 Oct 2023 17:39:41 +0300 Subject: [PATCH 014/128] Add lint tools, fix lint errors and warnings --- group/admin.py | 2 -- group/apps.py | 2 ++ group/models.py | 14 +++++++++----- group/serializers.py | 8 ++++++-- group/tests.py | 2 -- group/urls.py | 3 ++- group/views.py | 13 +++++++------ requirements.txt | 5 ++++- user/apps.py | 2 ++ user/models.py | 4 +--- user/serializers.py | 12 ++++++++---- user/tests.py | 2 -- user/urls.py | 4 +++- user/views.py | 20 +++++++++++++------- 14 files changed, 57 insertions(+), 36 deletions(-) diff --git a/group/admin.py b/group/admin.py index 8c38f3f..846f6b4 100644 --- a/group/admin.py +++ b/group/admin.py @@ -1,3 +1 @@ -from django.contrib import admin - # Register your models here. diff --git a/group/apps.py b/group/apps.py index d9c36a4..8b5d385 100644 --- a/group/apps.py +++ b/group/apps.py @@ -1,6 +1,8 @@ +"""Apps module""" from django.apps import AppConfig class GroupConfig(AppConfig): + """App class configuration""" default_auto_field = 'django.db.models.BigAutoField' name = 'group' diff --git a/group/models.py b/group/models.py index 8c2376b..1736aca 100644 --- a/group/models.py +++ b/group/models.py @@ -1,10 +1,14 @@ +"""Models module""" from django.db import models from django.contrib.auth.models import User + class Group(models.Model): - name=models.CharField(max_length=255, unique=True) - members=models.ManyToManyField(User, related_name='groups_joined') - creator=models.ForeignKey(User, on_delete=models.CASCADE, related_name='groups_created') + """Group class model""" + name = models.CharField(max_length=255, unique=True) + members = models.ManyToManyField(User, related_name='groups_joined') + creator = models.ForeignKey(User, on_delete=models.CASCADE, + related_name='groups_created') - def __str__(self): - return self.name \ No newline at end of file + def __str__(self) -> str: + return str(self.name) diff --git a/group/serializers.py b/group/serializers.py index 3ba22a3..2bd825f 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -1,7 +1,11 @@ +"""Serializer module""" from rest_framework import serializers from .models import Group + class GroupSerializer(serializers.ModelSerializer): - class Meta: + """Group seerializer class""" + class Meta: # pylint: disable=too-few-public-methods + """Serializer Meta class""" model = Group - fields = ['id', 'name', 'members', 'creator'] \ No newline at end of file + fields = ['id', 'name', 'members', 'creator'] diff --git a/group/tests.py b/group/tests.py index 7ce503c..a39b155 100644 --- a/group/tests.py +++ b/group/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/group/urls.py b/group/urls.py index 01fae8d..9ef0600 100644 --- a/group/urls.py +++ b/group/urls.py @@ -1,6 +1,7 @@ +"""urls module""" from django.urls import path from . import views urlpatterns = [ path('', views.create_group) -] \ No newline at end of file +] diff --git a/group/views.py b/group/views.py index 0dfee21..943defe 100644 --- a/group/views.py +++ b/group/views.py @@ -1,21 +1,22 @@ -from django.shortcuts import render +"""views module""" from rest_framework.decorators import api_view, permission_classes from rest_framework.response import Response from rest_framework import status -from .serializers import GroupSerializer from rest_framework.permissions import IsAuthenticated - +from .serializers import GroupSerializer # Create your views here. + @api_view(['POST']) @permission_classes([IsAuthenticated]) def create_group(request): + """function based view for creating a group""" if request.method == 'POST': data = request.data.copy() - data['creator']=request.user.id + data['creator'] = request.user.id serializer = GroupSerializer(data=data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return None diff --git a/requirements.txt b/requirements.txt index 6358434..99b9950 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,7 @@ django djangorestframework psycopg2 -django-environ \ No newline at end of file +django-environ +pylint +flake8 +pylint-django \ No newline at end of file diff --git a/user/apps.py b/user/apps.py index 36cce4c..93b93cf 100644 --- a/user/apps.py +++ b/user/apps.py @@ -1,6 +1,8 @@ +"""App module""" from django.apps import AppConfig class UserConfig(AppConfig): + """app class configuration""" default_auto_field = 'django.db.models.BigAutoField' name = 'user' diff --git a/user/models.py b/user/models.py index d49766e..6b20219 100644 --- a/user/models.py +++ b/user/models.py @@ -1,3 +1 @@ -from django.db import models - -# Create your models here. \ No newline at end of file +# Create your models here. diff --git a/user/serializers.py b/user/serializers.py index b520046..56a6354 100644 --- a/user/serializers.py +++ b/user/serializers.py @@ -1,16 +1,20 @@ +"""user serializer module""" from rest_framework import serializers from django.contrib.auth.models import User -from django.contrib.auth.hashers import make_password +from django.contrib.auth.hashers import make_password + class UserSerializer(serializers.ModelSerializer): - class Meta: + """user serializer class""" + class Meta: # pylint: disable=too-few-public-methods + """user serializer meta class""" model = User fields = ['id', 'username', 'password', 'email'] extra_kwargs = {'password': {'write_only': True}} - + def create(self, validated_data): password = validated_data.pop('password') user = User(**validated_data) user.password = make_password(password) user.save() - return user \ No newline at end of file + return user diff --git a/user/tests.py b/user/tests.py index 7ce503c..a39b155 100644 --- a/user/tests.py +++ b/user/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/user/urls.py b/user/urls.py index 6e6b636..0c41c75 100644 --- a/user/urls.py +++ b/user/urls.py @@ -1,7 +1,9 @@ +"""user urls module""" from django.urls import path from . import views + urlpatterns = [ path('signup/', views.create_user), path('signin/', views.signin_user), -] \ No newline at end of file +] diff --git a/user/views.py b/user/views.py index 23948ca..e3a2290 100644 --- a/user/views.py +++ b/user/views.py @@ -1,22 +1,27 @@ -from django.shortcuts import render -from .serializers import UserSerializer +"""user views module""" from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework import status from rest_framework.authtoken.models import Token from django.contrib.auth import authenticate +from .serializers import UserSerializer # Create your views here. - + + @api_view(['POST']) def create_user(request): + """a function for signing up a user""" if request.method == 'POST': serializer = UserSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) + return None + @api_view(['POST']) def signin_user(request): + """a function for signing in a user""" if request.method == 'POST': username = request.data.get('username') password = request.data.get('password') @@ -24,10 +29,11 @@ def signin_user(request): user = authenticate(username=username, password=password) if not user: return Response({ - 'error': 'username or password is incorrect'}, + 'error': 'username or password is incorrect'}, status=status.HTTP_401_UNAUTHORIZED) - token, _ = Token.objects.get_or_create(user=user) + token, _ = Token.objects.get_or_create(user=user)\ + # pylint: disable=no-member return Response({ - 'token': token.key}, + 'token': token.key}, status=status.HTTP_200_OK) - + return None From 9544e42adfca5ac1826ca6b07c1760a20a50f6fb Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 6 Nov 2023 15:12:51 +0300 Subject: [PATCH 015/128] Add pylintrc file --- group/serializers.py | 2 +- pylintrc | 636 +++++++++++++++++++++++++++++++++++++++++++ user/serializers.py | 2 +- user/views.py | 3 +- 4 files changed, 639 insertions(+), 4 deletions(-) create mode 100644 pylintrc diff --git a/group/serializers.py b/group/serializers.py index 2bd825f..f6bf418 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -5,7 +5,7 @@ class GroupSerializer(serializers.ModelSerializer): """Group seerializer class""" - class Meta: # pylint: disable=too-few-public-methods + class Meta: """Serializer Meta class""" model = Group fields = ['id', 'name', 'members', 'creator'] diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..eb2034b --- /dev/null +++ b/pylintrc @@ -0,0 +1,636 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.10 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + asyncSetUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=no-member, + too-few-public-methods, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + use-implicit-booleaness-not-comparison-to-string, + use-implicit-booleaness-not-comparison-to-zero + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are: text, parseable, colorized, +# json2 (improved json format), json (old json format) and msvs (visual +# studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/user/serializers.py b/user/serializers.py index 56a6354..d6f65c6 100644 --- a/user/serializers.py +++ b/user/serializers.py @@ -6,7 +6,7 @@ class UserSerializer(serializers.ModelSerializer): """user serializer class""" - class Meta: # pylint: disable=too-few-public-methods + class Meta: """user serializer meta class""" model = User fields = ['id', 'username', 'password', 'email'] diff --git a/user/views.py b/user/views.py index e3a2290..bb3d1ea 100644 --- a/user/views.py +++ b/user/views.py @@ -31,8 +31,7 @@ def signin_user(request): return Response({ 'error': 'username or password is incorrect'}, status=status.HTTP_401_UNAUTHORIZED) - token, _ = Token.objects.get_or_create(user=user)\ - # pylint: disable=no-member + token, _ = Token.objects.get_or_create(user=user) return Response({ 'token': token.key}, status=status.HTTP_200_OK) From 685c9c65b6b154a45e27033a2925ce5a7e8f1a52 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 8 Nov 2023 03:08:09 +0300 Subject: [PATCH 016/128] Use key directly for development --- post_it_backend/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py index 4d681ee..9ad6995 100644 --- a/post_it_backend/settings.py +++ b/post_it_backend/settings.py @@ -23,7 +23,7 @@ # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = env('SECRET_KEY') +SECRET_KEY = 'django-insecure-!l5_lbaz2+e6)&haqh@caqe_ixlr5oukfxuufqw+h3=5haztej' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True From 9199d0ef549a7c690634c1eb75b2956b65116ea6 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 8 Nov 2023 03:09:37 +0300 Subject: [PATCH 017/128] Update readme with project setup instructions --- README.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/README.md b/README.md index 186d4a8..7064397 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,55 @@ # post-it-backend PostIt ​is a simple application that allows friends and colleagues to create groups for notifications. This way one person can post notifications to everyone by sending a message once. The application allows people to create accounts, create groups add registered users to the groups, and then send messages to these groups whenever they want. + +# Project Setup + +1. After cloning the repo, set up a virtual environment + `python3 -m venv .venv` + +2. Activate the virtual environment + `. .venv/bin/activate` + +3. Install the dependencies in requirements.txt + `pip install -r requirements.txt` + +4. Configure PostgresSQL + i) Open the PostgresSQL command line interface + `psql postgres` + + ii) From your terminal, create a database + `CREATE DATABASE postit;` + + iii) Create a user, feel free to replace the username and password + `CREATE USER admin WITH PASSWORD 'password';` + + iv) Modify the connection parameters for the created user + `ALTER ROLE admin SET client_encoding TO 'utf8';` + `ALTER ROLE admin SET default_transaction_isolation TO 'read committed';` + `ALTER ROLE admin SET timezone TO 'UTC';` + + v) Grant the user access rights to the database + `GRANT ALL PRIVILEGES ON DATABASE postit TO admin;` + + vi) Exit the postgres command line + `\q` + +5. Create a .env file at the root of the project and set the database configurations there + `SECRET_KEY=your_secret_key` + `DB_NAME=postit` + `DB_USER=admin` + `DB_PASSWORD=your_password` + `DB_HOST=localhost` + `DB_PORT=5432` + +6. Make migrations + `python manage.py makemigrations` + +7. Migrate to create tables + `python manage.py migrate` + +8. Create a superuser + `python manage.py createsuperuser` + +9. Start the server + `python manage.py runserver` From d4a6068b59931a34323014bd191417eec5d31530 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 13 Nov 2023 11:07:17 +0300 Subject: [PATCH 018/128] Keep secret key in .env --- post_it_backend/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py index 9ad6995..4d681ee 100644 --- a/post_it_backend/settings.py +++ b/post_it_backend/settings.py @@ -23,7 +23,7 @@ # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-!l5_lbaz2+e6)&haqh@caqe_ixlr5oukfxuufqw+h3=5haztej' +SECRET_KEY = env('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True From 5c4c8cc57f9a2dbb59e8ee3a07adc2e7db23f8d8 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 13 Nov 2023 11:25:05 +0300 Subject: [PATCH 019/128] Register the Group model --- group/admin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/group/admin.py b/group/admin.py index 846f6b4..b1d0528 100644 --- a/group/admin.py +++ b/group/admin.py @@ -1 +1,5 @@ -# Register your models here. +"""Register the Group model to see it in Django admin""" +from django.contrib import admin +from .models import Group + +admin.site.register(Group) From 33c9433afd9a4f3f3a43bf663b660c0b334cd6b5 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 8 Jan 2024 16:36:28 +0300 Subject: [PATCH 020/128] Configure and setup docker --- .gitignore | 3 ++- Dockerfile | 29 +++++++++++++++++++++++++++++ docker-compose.dev.yml | 22 ++++++++++++++++++++++ post_it_backend/settings.py | 1 - 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.dev.yml diff --git a/.gitignore b/.gitignore index 293a1a2..58f79a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__/ .env -env/ \ No newline at end of file +env/ +docker-compose.yaml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8a7883d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM python:3.7 + +#declare envs +ARG SECRET_KEY +ARG DB_NAME +ARG DB_USER +ARG DB_PASSWORD +ARG DB_HOST +ARG DB_PORT + +#load envs +ENV SECRET_KEY=${SECRET_KEY} \ + DB_NAME=${DB_NAME} \ + DB_USER=${DB_USER} \ + DB_PASSWORD=${DB_PASSWORD} \ + DB_HOST=${DB_HOST} \ + DB_PORT=${DB_PORT} + +RUN mkdir /postit + +WORKDIR /postit + +COPY requirements.txt . + +RUN pip install -r requirements.txt + +EXPOSE 8000 + +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..ee4ded1 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,22 @@ +services: + db: + image: postgres + environment: + - POSTGRES_PASSWORD=admin + web: + build: . + command: python manage.py runserver 0.0.0.0:8000 + volumes: + - .:/postit + ports: + - "8000:8000" + depends_on: + - db + environment: + - SECRET_KEY= + - DB_NAME= + - DB_USER= + - DB_PASSWORD= + - DB_HOST= + - DB_PORT= + tty: true diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py index 4d681ee..ce985c9 100644 --- a/post_it_backend/settings.py +++ b/post_it_backend/settings.py @@ -17,7 +17,6 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) env = environ.Env() -environ.Env.read_env(env_file=str(BASE_DIR) + '/.env') # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ From 58f228e036cf91809799373ec15a599020bdc351 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 17 Jan 2024 22:57:51 +0300 Subject: [PATCH 021/128] Remove mkdir command --- Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8a7883d..5d9333c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,8 +16,6 @@ ENV SECRET_KEY=${SECRET_KEY} \ DB_HOST=${DB_HOST} \ DB_PORT=${DB_PORT} -RUN mkdir /postit - WORKDIR /postit COPY requirements.txt . From 82e1261d89289bd9582ca2dea417664d07d1125f Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 17 Jan 2024 22:58:42 +0300 Subject: [PATCH 022/128] Remove compose file from gitignore, add env_file --- .gitignore | 3 +-- docker-compose.yml | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 docker-compose.yml diff --git a/.gitignore b/.gitignore index 58f79a0..293a1a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ __pycache__/ .env -env/ -docker-compose.yaml \ No newline at end of file +env/ \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..00af9de --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + db: + image: postgres + environment: + - POSTGRES_PASSWORD=admin + web: + build: . + command: python manage.py runserver 0.0.0.0:8000 + volumes: + - .:/postit + ports: + - "8000:8000" + depends_on: + - db + environment: + - SECRET_KEY=${SECRET_KEY} + - DB_NAME=${DB_NAME} + - DB_USER=${DB_USER} + - DB_PASSWORD=${DB_PASSWORD} + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + env_file: + - .env + tty: true From 6df7df6d47e05529cd714cdcec3f6d946f2faceb Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 18 Jan 2024 12:51:32 +0300 Subject: [PATCH 023/128] Remove variables from Dockerfile --- Dockerfile | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5d9333c..3b39b52 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,5 @@ FROM python:3.7 -#declare envs -ARG SECRET_KEY -ARG DB_NAME -ARG DB_USER -ARG DB_PASSWORD -ARG DB_HOST -ARG DB_PORT - -#load envs -ENV SECRET_KEY=${SECRET_KEY} \ - DB_NAME=${DB_NAME} \ - DB_USER=${DB_USER} \ - DB_PASSWORD=${DB_PASSWORD} \ - DB_HOST=${DB_HOST} \ - DB_PORT=${DB_PORT} - WORKDIR /postit COPY requirements.txt . From ecc3e72dbf7b4526cc0ce5cdb925c3226a4c3f64 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 18 Jan 2024 13:12:48 +0300 Subject: [PATCH 024/128] Move postgres password to env file --- docker-compose.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 00af9de..b3535b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,9 @@ services: db: image: postgres environment: - - POSTGRES_PASSWORD=admin + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + env_file: + - .env web: build: . command: python manage.py runserver 0.0.0.0:8000 From f63d100d478feb85c1d8c00d11710d5b9dd4c702 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 18 Jan 2024 13:14:03 +0300 Subject: [PATCH 025/128] Update docker-compose.dev file --- docker-compose.dev.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index ee4ded1..b3535b7 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -2,7 +2,9 @@ services: db: image: postgres environment: - - POSTGRES_PASSWORD=admin + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + env_file: + - .env web: build: . command: python manage.py runserver 0.0.0.0:8000 @@ -13,10 +15,12 @@ services: depends_on: - db environment: - - SECRET_KEY= - - DB_NAME= - - DB_USER= - - DB_PASSWORD= - - DB_HOST= - - DB_PORT= + - SECRET_KEY=${SECRET_KEY} + - DB_NAME=${DB_NAME} + - DB_USER=${DB_USER} + - DB_PASSWORD=${DB_PASSWORD} + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + env_file: + - .env tty: true From e3129aacd0fa9c7343fea8adfcde505593ffca90 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 22 Jan 2024 17:19:05 +0300 Subject: [PATCH 026/128] Remove env_file --- docker-compose.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b3535b7..0276063 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,8 +3,6 @@ services: image: postgres environment: - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - env_file: - - .env web: build: . command: python manage.py runserver 0.0.0.0:8000 @@ -21,6 +19,4 @@ services: - DB_PASSWORD=${DB_PASSWORD} - DB_HOST=${DB_HOST} - DB_PORT=${DB_PORT} - env_file: - - .env tty: true From 696b43c8c91fd94ec15e826b6d18d0420e254593 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 22 Feb 2024 11:08:36 +0300 Subject: [PATCH 027/128] Update to python 3.9 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3b39b52..6ab5314 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.7 +FROM python:3.9 WORKDIR /postit From 1bdcf807879eadff6002cd9c58e717f2c5241b77 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 22 Feb 2024 11:36:03 +0300 Subject: [PATCH 028/128] Add explicit versions for requirements --- requirements.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index 99b9950..d34b032 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -django -djangorestframework -psycopg2 -django-environ -pylint -flake8 -pylint-django \ No newline at end of file +Django==4.2.6 +django-environ==0.11.2 +djangorestframework==3.14.0 +flake8==6.1.0 +psycopg2==2.9.9 +pylint==3.0.2 +pylint-django==2.5.5 \ No newline at end of file From 0f2ec1997ea3ea315341826693e9db982b47ff11 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 2 Nov 2023 12:26:53 +0300 Subject: [PATCH 029/128] Create api to add users to a group --- group/urls.py | 3 ++- group/views.py | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/group/urls.py b/group/urls.py index 9ef0600..3c8e839 100644 --- a/group/urls.py +++ b/group/urls.py @@ -3,5 +3,6 @@ from . import views urlpatterns = [ - path('', views.create_group) + path('', views.create_group), + path('/user/', views.add_users), ] diff --git a/group/views.py b/group/views.py index 943defe..05b023e 100644 --- a/group/views.py +++ b/group/views.py @@ -3,7 +3,10 @@ from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated +from django.contrib.auth.models import User +from .models import Group from .serializers import GroupSerializer + # Create your views here. @@ -18,5 +21,33 @@ def create_group(request): if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - return None + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def add_users(request, group_id): + """Add user(s) to a group""" + if request.method == 'POST': + members = request.data.get('members') + try: + group = Group.objects.get(id=group_id) + except Group.DoesNotExist: + return Response({"error":"Group not found"}, status=status.HTTP_404_NOT_FOUND) + + for member in group.members.all(): + if member.id not in members: + members.append(member.id) + + for member in members: + user = User.objects.get(id=member) + if user.id not in group.members.all().values_list('id', flat=True): + group.members.add(user) + + serializer = GroupSerializer(group, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file From 61f33a7ccbeab59653078d2388407a43ab7aecc1 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 14 Nov 2023 14:59:46 +0300 Subject: [PATCH 030/128] Fix lint warnings --- group/views.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/group/views.py b/group/views.py index 05b023e..02afa59 100644 --- a/group/views.py +++ b/group/views.py @@ -21,8 +21,8 @@ def create_group(request): if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return None @api_view(['POST']) @@ -34,20 +34,18 @@ def add_users(request, group_id): try: group = Group.objects.get(id=group_id) except Group.DoesNotExist: - return Response({"error":"Group not found"}, status=status.HTTP_404_NOT_FOUND) - + return Response({"error": "Group not found"}, + status=status.HTTP_404_NOT_FOUND) for member in group.members.all(): if member.id not in members: members.append(member.id) - for member in members: user = User.objects.get(id=member) if user.id not in group.members.all().values_list('id', flat=True): group.members.add(user) - serializer = GroupSerializer(group, data=request.data, partial=True) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return None From 527a30c359c5277e3a51363efb5203f0623b824d Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 14 Nov 2023 15:42:14 +0300 Subject: [PATCH 031/128] Create api to delete a group --- group/urls.py | 1 + group/views.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/group/urls.py b/group/urls.py index 3c8e839..4c84312 100644 --- a/group/urls.py +++ b/group/urls.py @@ -5,4 +5,5 @@ urlpatterns = [ path('', views.create_group), path('/user/', views.add_users), + path('/delete/', views.delete_group), ] diff --git a/group/views.py b/group/views.py index 02afa59..0d5eb56 100644 --- a/group/views.py +++ b/group/views.py @@ -49,3 +49,21 @@ def add_users(request, group_id): return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return None + + +@api_view(['DELETE']) +@permission_classes([IsAuthenticated]) +def delete_group(request, group_id): + """Delete a group""" + if request.method == 'DELETE': + try: + group = Group.objects.get(id=group_id) + except Group.DoesNotExist: + return Response({"error": "Group not found"}, + status=status.HTTP_404_NOT_FOUND) + if request.user == group.creator: + group.delete() + return Response({"message": "Group deleted successfully"}, + status=status.HTTP_204_NO_CONTENT) + return Response({"error": "Only the group creator can delete"}) + return None From f7a155f5d290561810a1306677a5049ed335d20e Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 15 Nov 2023 17:45:18 +0300 Subject: [PATCH 032/128] Disable too many return statements lint rule --- pylintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pylintrc b/pylintrc index eb2034b..1c66657 100644 --- a/pylintrc +++ b/pylintrc @@ -421,7 +421,8 @@ confidence=HIGH, # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". -disable=no-member, +disable=too-many-return-statements, + no-member, too-few-public-methods, raw-checker-failed, bad-inline-option, From 5b1a712ecc3ced0b92c148ed0d9dee9a51fdae2f Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 15 Nov 2023 17:46:02 +0300 Subject: [PATCH 033/128] Create API to remove a user from a group --- group/urls.py | 1 + group/views.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/group/urls.py b/group/urls.py index 4c84312..1ee9e41 100644 --- a/group/urls.py +++ b/group/urls.py @@ -6,4 +6,5 @@ path('', views.create_group), path('/user/', views.add_users), path('/delete/', views.delete_group), + path('/remove/', views.remove_user), ] diff --git a/group/views.py b/group/views.py index 0d5eb56..c7eff34 100644 --- a/group/views.py +++ b/group/views.py @@ -67,3 +67,34 @@ def delete_group(request, group_id): status=status.HTTP_204_NO_CONTENT) return Response({"error": "Only the group creator can delete"}) return None + + +@api_view(['DELETE']) +@permission_classes([IsAuthenticated]) +def remove_user(request, group_id, user_id): + """Remove a user(s) from a group""" + if request.method == 'DELETE': + try: + group = Group.objects.get(id=group_id) + except Group.DoesNotExist: + return Response({"error": "Group not found"}, + status=status.HTTP_404_NOT_FOUND) + if request.user == group.creator: + try: + user = User.objects.get(id=user_id) + if user == group.creator: + return Response( + {"error": "Cannot remove creator from the group"}, + status=status.HTTP_403_FORBIDDEN) + if user.id not in group.members.all().values_list('id', + flat=True): + return Response( + {"error": f"User with ID {user_id} not in this group"}) + group.members.remove(user) + except User.DoesNotExist: + return Response({"error": f"User with ID {user_id} not found"}, + status=status.HTTP_404_NOT_FOUND) + return Response({"message": "Member(s) successfully removed"}, + status=status.HTTP_204_NO_CONTENT) + return Response({"error": "Only the group creator can remove members"}) + return None From 1636f56c5cd0a1d15aef14f70fbbace66b1657d8 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 16 Nov 2023 13:16:45 +0300 Subject: [PATCH 034/128] Add all valid users and throw errors after --- group/views.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/group/views.py b/group/views.py index c7eff34..8089b68 100644 --- a/group/views.py +++ b/group/views.py @@ -30,23 +30,34 @@ def create_group(request): def add_users(request, group_id): """Add user(s) to a group""" if request.method == 'POST': - members = request.data.get('members') + data = request.data.copy() + + data.pop('creator', None) + data.pop('name', None) try: group = Group.objects.get(id=group_id) except Group.DoesNotExist: return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) + errors = [] for member in group.members.all(): - if member.id not in members: - members.append(member.id) - for member in members: - user = User.objects.get(id=member) - if user.id not in group.members.all().values_list('id', flat=True): - group.members.add(user) - serializer = GroupSerializer(group, data=request.data, partial=True) + if member.id not in data['members']: + data['members'].append(member.id) + for member in data['members']: + try: + user = User.objects.get(id=member) + if user.id not in group.members.all().values_list('id', + flat=True): + group.members.add(user) + except User.DoesNotExist: + errors.append(f"User with ID {member} is not found") + serializer = GroupSerializer(group, data=data, partial=True) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) + if errors: + return Response({"error": errors}, + status=status.HTTP_404_NOT_FOUND) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return None From 1243b692c5930b8d675c8a02f14e1e34bce17bc9 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 20 Nov 2023 12:18:36 +0300 Subject: [PATCH 035/128] Remove group id from path --- group/urls.py | 2 +- group/views.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/group/urls.py b/group/urls.py index 1ee9e41..7326626 100644 --- a/group/urls.py +++ b/group/urls.py @@ -4,7 +4,7 @@ urlpatterns = [ path('', views.create_group), - path('/user/', views.add_users), + path('user/', views.add_users), path('/delete/', views.delete_group), path('/remove/', views.remove_user), ] diff --git a/group/views.py b/group/views.py index 8089b68..5611161 100644 --- a/group/views.py +++ b/group/views.py @@ -27,10 +27,11 @@ def create_group(request): @api_view(['POST']) @permission_classes([IsAuthenticated]) -def add_users(request, group_id): +def add_users(request): """Add user(s) to a group""" if request.method == 'POST': data = request.data.copy() + group_id = request.data.get("group_id") data.pop('creator', None) data.pop('name', None) From 68050ad0297c25d24b371a9c4a275482b87ba122 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 20 Nov 2023 12:47:27 +0300 Subject: [PATCH 036/128] Change request method to PATCH --- group/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/group/views.py b/group/views.py index 5611161..4315ca1 100644 --- a/group/views.py +++ b/group/views.py @@ -25,11 +25,11 @@ def create_group(request): return None -@api_view(['POST']) +@api_view(['PATCH']) @permission_classes([IsAuthenticated]) def add_users(request): """Add user(s) to a group""" - if request.method == 'POST': + if request.method == 'PATCH': data = request.data.copy() group_id = request.data.get("group_id") From 91be5f59458f6ae7e730960196632aaf53d0df2b Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 20 Nov 2023 12:55:14 +0300 Subject: [PATCH 037/128] Move logic inside group try block --- group/views.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/group/views.py b/group/views.py index 4315ca1..5f809d2 100644 --- a/group/views.py +++ b/group/views.py @@ -37,29 +37,30 @@ def add_users(request): data.pop('name', None) try: group = Group.objects.get(id=group_id) + errors = [] + for member in group.members.all(): + if member.id not in data['members']: + data['members'].append(member.id) + for member in data['members']: + try: + user = User.objects.get(id=member) + if user.id not in group.members.all()\ + .values_list('id', flat=True): + group.members.add(user) + except User.DoesNotExist: + errors.append(f"User with ID {member} is not found") + serializer = GroupSerializer(group, data=data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + if errors: + return Response({"error": errors}, + status=status.HTTP_404_NOT_FOUND) + return Response(serializer.errors, + status=status.HTTP_400_BAD_REQUEST) except Group.DoesNotExist: return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) - errors = [] - for member in group.members.all(): - if member.id not in data['members']: - data['members'].append(member.id) - for member in data['members']: - try: - user = User.objects.get(id=member) - if user.id not in group.members.all().values_list('id', - flat=True): - group.members.add(user) - except User.DoesNotExist: - errors.append(f"User with ID {member} is not found") - serializer = GroupSerializer(group, data=data, partial=True) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status=status.HTTP_200_OK) - if errors: - return Response({"error": errors}, - status=status.HTTP_404_NOT_FOUND) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return None From 119a30ae514b9b6e102a9733693fe3a9c9dc9ede Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 20 Nov 2023 13:38:59 +0300 Subject: [PATCH 038/128] Allow only group creator to add members --- group/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/group/views.py b/group/views.py index 5f809d2..70879a5 100644 --- a/group/views.py +++ b/group/views.py @@ -37,6 +37,10 @@ def add_users(request): data.pop('name', None) try: group = Group.objects.get(id=group_id) + if request.user != group.creator: + return Response({ + "error": "Only group creator can add members"}, + status=status.HTTP_403_FORBIDDEN) errors = [] for member in group.members.all(): if member.id not in data['members']: From db84e4ccbab2a16dd9d6e38b92c8ae88b54169d8 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 20 Nov 2023 13:56:19 +0300 Subject: [PATCH 039/128] Remove group id from delete group url path --- group/urls.py | 2 +- group/views.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/group/urls.py b/group/urls.py index 7326626..b42b3ea 100644 --- a/group/urls.py +++ b/group/urls.py @@ -5,6 +5,6 @@ urlpatterns = [ path('', views.create_group), path('user/', views.add_users), - path('/delete/', views.delete_group), + path('delete/', views.delete_group), path('/remove/', views.remove_user), ] diff --git a/group/views.py b/group/views.py index 70879a5..5ac0932 100644 --- a/group/views.py +++ b/group/views.py @@ -70,9 +70,10 @@ def add_users(request): @api_view(['DELETE']) @permission_classes([IsAuthenticated]) -def delete_group(request, group_id): +def delete_group(request): """Delete a group""" if request.method == 'DELETE': + group_id = request.data.get("group_id") try: group = Group.objects.get(id=group_id) except Group.DoesNotExist: From d7052a3b84e82965c3031192b639fec054078a0d Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 20 Nov 2023 14:01:51 +0300 Subject: [PATCH 040/128] Move delete group logic inside try block --- group/views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/group/views.py b/group/views.py index 5ac0932..6718ba5 100644 --- a/group/views.py +++ b/group/views.py @@ -76,14 +76,14 @@ def delete_group(request): group_id = request.data.get("group_id") try: group = Group.objects.get(id=group_id) - except Group.DoesNotExist: - return Response({"error": "Group not found"}, - status=status.HTTP_404_NOT_FOUND) - if request.user == group.creator: + if request.user != group.creator: + return Response({"error": "Only the group creator can delete"}) group.delete() return Response({"message": "Group deleted successfully"}, status=status.HTTP_204_NO_CONTENT) - return Response({"error": "Only the group creator can delete"}) + except Group.DoesNotExist: + return Response({"error": "Group not found"}, + status=status.HTTP_404_NOT_FOUND) return None From 6482a47407061a9366698b4c821b8be1c509ce44 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 20 Nov 2023 14:08:10 +0300 Subject: [PATCH 041/128] Remove group and user id from path --- group/urls.py | 2 +- group/views.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/group/urls.py b/group/urls.py index b42b3ea..40cacb1 100644 --- a/group/urls.py +++ b/group/urls.py @@ -6,5 +6,5 @@ path('', views.create_group), path('user/', views.add_users), path('delete/', views.delete_group), - path('/remove/', views.remove_user), + path('remove/', views.remove_user), ] diff --git a/group/views.py b/group/views.py index 6718ba5..eb3c439 100644 --- a/group/views.py +++ b/group/views.py @@ -89,9 +89,11 @@ def delete_group(request): @api_view(['DELETE']) @permission_classes([IsAuthenticated]) -def remove_user(request, group_id, user_id): +def remove_user(request): """Remove a user(s) from a group""" if request.method == 'DELETE': + group_id = request.data.get("group_id") + user_id = request.data.get("user_id") try: group = Group.objects.get(id=group_id) except Group.DoesNotExist: From 0987f38a55715bfa193e2e441caf9cb6c18232c2 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 20 Nov 2023 14:15:59 +0300 Subject: [PATCH 042/128] Move remove user logic inside try block --- group/views.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/group/views.py b/group/views.py index eb3c439..5a3e430 100644 --- a/group/views.py +++ b/group/views.py @@ -96,10 +96,10 @@ def remove_user(request): user_id = request.data.get("user_id") try: group = Group.objects.get(id=group_id) - except Group.DoesNotExist: - return Response({"error": "Group not found"}, - status=status.HTTP_404_NOT_FOUND) - if request.user == group.creator: + if request.user != group.creator: + return Response({ + "error": + "Only group creator can remove members"}) try: user = User.objects.get(id=user_id) if user == group.creator: @@ -116,5 +116,7 @@ def remove_user(request): status=status.HTTP_404_NOT_FOUND) return Response({"message": "Member(s) successfully removed"}, status=status.HTTP_204_NO_CONTENT) - return Response({"error": "Only the group creator can remove members"}) + except Group.DoesNotExist: + return Response({"error": "Group not found"}, + status=status.HTTP_404_NOT_FOUND) return None From 4332ff0ce3ecb58454237556ebbdc24c94ddecdb Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 22 Nov 2023 01:59:40 +0300 Subject: [PATCH 043/128] Modify API functions to use class based views --- group/urls.py | 9 ++++----- group/views.py | 50 +++++++++++++++++++------------------------------- 2 files changed, 23 insertions(+), 36 deletions(-) diff --git a/group/urls.py b/group/urls.py index 40cacb1..d0e64e4 100644 --- a/group/urls.py +++ b/group/urls.py @@ -1,10 +1,9 @@ """urls module""" from django.urls import path -from . import views +from .views import PostItGroupApiView, PostItGroupDetailApiView urlpatterns = [ - path('', views.create_group), - path('user/', views.add_users), - path('delete/', views.delete_group), - path('remove/', views.remove_user), + path('', PostItGroupApiView.as_view()), + path('/', PostItGroupApiView.as_view()), + path('remove_user//', PostItGroupDetailApiView.as_view()), ] diff --git a/group/views.py b/group/views.py index 5a3e430..bcfa6d9 100644 --- a/group/views.py +++ b/group/views.py @@ -1,20 +1,19 @@ """views module""" -from rest_framework.decorators import api_view, permission_classes +from rest_framework.views import APIView +from rest_framework import permissions from rest_framework.response import Response from rest_framework import status -from rest_framework.permissions import IsAuthenticated from django.contrib.auth.models import User from .models import Group from .serializers import GroupSerializer -# Create your views here. +class PostItGroupApiView(APIView): + """Define methods for performing actions on groups""" + permission_classes = [permissions.IsAuthenticated] -@api_view(['POST']) -@permission_classes([IsAuthenticated]) -def create_group(request): - """function based view for creating a group""" - if request.method == 'POST': + def post(self, request): + """Create a group""" data = request.data.copy() data['creator'] = request.user.id serializer = GroupSerializer(data=data) @@ -22,14 +21,9 @@ def create_group(request): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - return None - -@api_view(['PATCH']) -@permission_classes([IsAuthenticated]) -def add_users(request): - """Add user(s) to a group""" - if request.method == 'PATCH': + def patch(self, request): + """Update members list""" data = request.data.copy() group_id = request.data.get("group_id") @@ -65,15 +59,9 @@ def add_users(request): except Group.DoesNotExist: return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) - return None - -@api_view(['DELETE']) -@permission_classes([IsAuthenticated]) -def delete_group(request): - """Delete a group""" - if request.method == 'DELETE': - group_id = request.data.get("group_id") + def delete(self, request, group_id): + """Delete a group""" try: group = Group.objects.get(id=group_id) if request.user != group.creator: @@ -84,16 +72,17 @@ def delete_group(request): except Group.DoesNotExist: return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) - return None -@api_view(['DELETE']) -@permission_classes([IsAuthenticated]) -def remove_user(request): - """Remove a user(s) from a group""" - if request.method == 'DELETE': +class PostItGroupDetailApiView(APIView): + """ + Define methods for performing detail and more specific actions on groups + """ + permission_classes = [permissions.IsAuthenticated] + + def delete(self, request, user_id): + """Delete/Remove a user from a specific group""" group_id = request.data.get("group_id") - user_id = request.data.get("user_id") try: group = Group.objects.get(id=group_id) if request.user != group.creator: @@ -119,4 +108,3 @@ def remove_user(request): except Group.DoesNotExist: return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) - return None From d2770aa415ef6360d18850cfedcd017ed14f31a7 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 22 Nov 2023 23:28:09 +0300 Subject: [PATCH 044/128] Update members list --- group/serializers.py | 12 ++++++++++++ group/views.py | 9 +++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/group/serializers.py b/group/serializers.py index f6bf418..acfc631 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -1,5 +1,6 @@ """Serializer module""" from rest_framework import serializers +from django.contrib.auth.models import User from .models import Group @@ -9,3 +10,14 @@ class Meta: """Serializer Meta class""" model = Group fields = ['id', 'name', 'members', 'creator'] + + def update(self, instance, validated_data): + existing_members = instance.members.all() + new_members = validated_data.get("members") + + for member in new_members: + if member not in existing_members: + user = User.objects.get(id=member) + instance.members.add(user) + instance.save() + return instance diff --git a/group/views.py b/group/views.py index bcfa6d9..c9d3b7f 100644 --- a/group/views.py +++ b/group/views.py @@ -24,8 +24,8 @@ def post(self, request): def patch(self, request): """Update members list""" - data = request.data.copy() - group_id = request.data.get("group_id") + data = request.data + group_id = data.get("group_id") data.pop('creator', None) data.pop('name', None) @@ -36,10 +36,7 @@ def patch(self, request): "error": "Only group creator can add members"}, status=status.HTTP_403_FORBIDDEN) errors = [] - for member in group.members.all(): - if member.id not in data['members']: - data['members'].append(member.id) - for member in data['members']: + for member in data.get("members"): try: user = User.objects.get(id=member) if user.id not in group.members.all()\ From 3f8a643f0f7829d5c739b2e58a1838a5c6a80c74 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 22 Nov 2023 23:44:14 +0300 Subject: [PATCH 045/128] Use one try block --- group/views.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/group/views.py b/group/views.py index c9d3b7f..a007136 100644 --- a/group/views.py +++ b/group/views.py @@ -86,22 +86,22 @@ def delete(self, request, user_id): return Response({ "error": "Only group creator can remove members"}) - try: - user = User.objects.get(id=user_id) - if user == group.creator: - return Response( - {"error": "Cannot remove creator from the group"}, - status=status.HTTP_403_FORBIDDEN) - if user.id not in group.members.all().values_list('id', - flat=True): - return Response( - {"error": f"User with ID {user_id} not in this group"}) - group.members.remove(user) - except User.DoesNotExist: - return Response({"error": f"User with ID {user_id} not found"}, - status=status.HTTP_404_NOT_FOUND) - return Response({"message": "Member(s) successfully removed"}, + user = User.objects.get(id=user_id) + if user == group.creator: + return Response( + {"error": "Cannot remove creator from the group"}, + status=status.HTTP_403_FORBIDDEN) + if user.id not in group.members.all().values_list('id', + flat=True): + return Response( + {"error": f"User with ID {user_id} not in this group"}) + group.members.remove(user) + return Response({"message": + f"User with ID {user_id} successfully removed"}, status=status.HTTP_204_NO_CONTENT) + except User.DoesNotExist: + return Response({"error": f"User with ID {user_id} not found"}, + status=status.HTTP_404_NOT_FOUND) except Group.DoesNotExist: return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) From de1ba3edf44b1d512d72d443db91a9ead04a8398 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 23 Nov 2023 22:51:44 +0300 Subject: [PATCH 046/128] Remove Postit prefix in class name --- group/urls.py | 8 ++++---- group/views.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/group/urls.py b/group/urls.py index d0e64e4..e36f91b 100644 --- a/group/urls.py +++ b/group/urls.py @@ -1,9 +1,9 @@ """urls module""" from django.urls import path -from .views import PostItGroupApiView, PostItGroupDetailApiView +from .views import GroupApiView, GroupDetailApiView urlpatterns = [ - path('', PostItGroupApiView.as_view()), - path('/', PostItGroupApiView.as_view()), - path('remove_user//', PostItGroupDetailApiView.as_view()), + path('', GroupApiView.as_view()), + path('/', GroupApiView.as_view()), + path('remove_user//', GroupDetailApiView.as_view()), ] diff --git a/group/views.py b/group/views.py index a007136..3d69cf1 100644 --- a/group/views.py +++ b/group/views.py @@ -8,7 +8,7 @@ from .serializers import GroupSerializer -class PostItGroupApiView(APIView): +class GroupApiView(APIView): """Define methods for performing actions on groups""" permission_classes = [permissions.IsAuthenticated] @@ -71,7 +71,7 @@ def delete(self, request, group_id): status=status.HTTP_404_NOT_FOUND) -class PostItGroupDetailApiView(APIView): +class GroupDetailApiView(APIView): """ Define methods for performing detail and more specific actions on groups """ From 77adf840452a4895d52fca5989fad49759f8633d Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 23 Nov 2023 22:54:05 +0300 Subject: [PATCH 047/128] Rename path --- group/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/group/urls.py b/group/urls.py index e36f91b..453c418 100644 --- a/group/urls.py +++ b/group/urls.py @@ -5,5 +5,5 @@ urlpatterns = [ path('', GroupApiView.as_view()), path('/', GroupApiView.as_view()), - path('remove_user//', GroupDetailApiView.as_view()), + path('user//', GroupDetailApiView.as_view()), ] From c6de203635cd6527f721a931b520af800edc9301 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 24 Nov 2023 12:02:41 +0300 Subject: [PATCH 048/128] Abstract group and permission check --- group/views.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/group/views.py b/group/views.py index 3d69cf1..73fa7f1 100644 --- a/group/views.py +++ b/group/views.py @@ -1,6 +1,8 @@ """views module""" from rest_framework.views import APIView from rest_framework import permissions +from django.http import Http404 +from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response from rest_framework import status from django.contrib.auth.models import User @@ -12,6 +14,15 @@ class GroupApiView(APIView): """Define methods for performing actions on groups""" permission_classes = [permissions.IsAuthenticated] + def get_object(self, request, group_id): + try: + group = Group.objects.get(id=group_id) + if request.user != group.creator: + raise PermissionDenied("Access Denied") + return group + except Group.DoesNotExist: + raise Http404 + def post(self, request): """Create a group""" data = request.data.copy() @@ -30,11 +41,7 @@ def patch(self, request): data.pop('creator', None) data.pop('name', None) try: - group = Group.objects.get(id=group_id) - if request.user != group.creator: - return Response({ - "error": "Only group creator can add members"}, - status=status.HTTP_403_FORBIDDEN) + group = self.get_object(request, group_id) errors = [] for member in data.get("members"): try: @@ -53,20 +60,24 @@ def patch(self, request): status=status.HTTP_404_NOT_FOUND) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - except Group.DoesNotExist: + except PermissionDenied: + return Response({"error": "Only group creator can add members"}, + status=status.HTTP_403_FORBIDDEN) + except Http404: return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) def delete(self, request, group_id): """Delete a group""" try: - group = Group.objects.get(id=group_id) - if request.user != group.creator: - return Response({"error": "Only the group creator can delete"}) + group = self.get_object(request, group_id) group.delete() return Response({"message": "Group deleted successfully"}, - status=status.HTTP_204_NO_CONTENT) - except Group.DoesNotExist: + status=status.HTTP_204_NO_CONTENT) + except PermissionDenied: + return Response({"error": "Only the group creator can delete"}, + status=status.HTTP_403_FORBIDDEN) + except Http404: return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) From a652c525778a3c8fdcaf5fbd98b89c80bb779a8d Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 24 Nov 2023 14:41:11 +0300 Subject: [PATCH 049/128] Add get method to retrieve all groups --- group/views.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/group/views.py b/group/views.py index 73fa7f1..5fd3bf5 100644 --- a/group/views.py +++ b/group/views.py @@ -22,7 +22,13 @@ def get_object(self, request, group_id): return group except Group.DoesNotExist: raise Http404 - + + def get(self, request): + """Retrieve a list of all the groups""" + groups = Group.objects.all() + serializer = GroupSerializer(groups, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + def post(self, request): """Create a group""" data = request.data.copy() From 87593746948a37a8cd2aa2c70ff4f4927cd3a567 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 24 Nov 2023 14:46:37 +0300 Subject: [PATCH 050/128] Disable unused-argument lint warning --- pylintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pylintrc b/pylintrc index 1c66657..6777ccb 100644 --- a/pylintrc +++ b/pylintrc @@ -421,7 +421,8 @@ confidence=HIGH, # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". -disable=too-many-return-statements, +disable=unused-argument, + too-many-return-statements, no-member, too-few-public-methods, raw-checker-failed, From 979e1caad8ce92e76553f9658d656a4a2ed6c1d0 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 24 Nov 2023 15:08:38 +0300 Subject: [PATCH 051/128] Add get method to retrieve a single group --- group/urls.py | 1 + group/views.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/group/urls.py b/group/urls.py index 453c418..57e123e 100644 --- a/group/urls.py +++ b/group/urls.py @@ -5,5 +5,6 @@ urlpatterns = [ path('', GroupApiView.as_view()), path('/', GroupApiView.as_view()), + path('detail//', GroupDetailApiView.as_view()), path('user//', GroupDetailApiView.as_view()), ] diff --git a/group/views.py b/group/views.py index 5fd3bf5..eaab748 100644 --- a/group/views.py +++ b/group/views.py @@ -94,6 +94,16 @@ class GroupDetailApiView(APIView): """ permission_classes = [permissions.IsAuthenticated] + def get(self, request, group_id): + """Retrieve a single group""" + try: + group = Group.objects.get(id=group_id) + serializer = GroupSerializer(group) + return Response(serializer.data, status=status.HTTP_200_OK) + except Group.DoesNotExist: + return Response({"error": "Group not found"}, + status=status.HTTP_404_NOT_FOUND) + def delete(self, request, user_id): """Delete/Remove a user from a specific group""" group_id = request.data.get("group_id") From c1101404853aa182421ef18fa032b5bab7932f9e Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 24 Nov 2023 15:18:33 +0300 Subject: [PATCH 052/128] Fix lint warnings --- group/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/group/views.py b/group/views.py index eaab748..43ffe0a 100644 --- a/group/views.py +++ b/group/views.py @@ -1,11 +1,11 @@ """views module""" from rest_framework.views import APIView from rest_framework import permissions -from django.http import Http404 from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response from rest_framework import status from django.contrib.auth.models import User +from django.http import Http404 from .models import Group from .serializers import GroupSerializer @@ -15,13 +15,14 @@ class GroupApiView(APIView): permission_classes = [permissions.IsAuthenticated] def get_object(self, request, group_id): + """Check for user permission and group existence""" try: group = Group.objects.get(id=group_id) if request.user != group.creator: raise PermissionDenied("Access Denied") return group - except Group.DoesNotExist: - raise Http404 + except Group.DoesNotExist as exc: + raise Http404 from exc def get(self, request): """Retrieve a list of all the groups""" @@ -79,7 +80,7 @@ def delete(self, request, group_id): group = self.get_object(request, group_id) group.delete() return Response({"message": "Group deleted successfully"}, - status=status.HTTP_204_NO_CONTENT) + status=status.HTTP_204_NO_CONTENT) except PermissionDenied: return Response({"error": "Only the group creator can delete"}, status=status.HTTP_403_FORBIDDEN) From 5f79876f9fc81d9744c2a0ceaa7c9c78cace7979 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 30 Nov 2023 13:33:15 +0300 Subject: [PATCH 053/128] Return member objects in get method --- group/serializers.py | 8 ++++++++ group/views.py | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/group/serializers.py b/group/serializers.py index acfc631..3908361 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -2,7 +2,15 @@ from rest_framework import serializers from django.contrib.auth.models import User from .models import Group +from user.serializers import UserSerializer +class GroupGetSerializer(serializers.ModelSerializer): + """Group seerializer class""" + members = UserSerializer(many=True) + class Meta: + """Serializer Meta class""" + model = Group + fields = ['id', 'name', 'members', 'creator'] class GroupSerializer(serializers.ModelSerializer): """Group seerializer class""" diff --git a/group/views.py b/group/views.py index 43ffe0a..4530764 100644 --- a/group/views.py +++ b/group/views.py @@ -8,6 +8,7 @@ from django.http import Http404 from .models import Group from .serializers import GroupSerializer +from .serializers import GroupGetSerializer class GroupApiView(APIView): @@ -27,7 +28,7 @@ def get_object(self, request, group_id): def get(self, request): """Retrieve a list of all the groups""" groups = Group.objects.all() - serializer = GroupSerializer(groups, many=True) + serializer = GroupGetSerializer(groups, many=True) return Response(serializer.data, status=status.HTTP_200_OK) def post(self, request): @@ -99,7 +100,7 @@ def get(self, request, group_id): """Retrieve a single group""" try: group = Group.objects.get(id=group_id) - serializer = GroupSerializer(group) + serializer = GroupGetSerializer(group) return Response(serializer.data, status=status.HTTP_200_OK) except Group.DoesNotExist: return Response({"error": "Group not found"}, From 62aa855e563b84fe8490572053d726557f797331 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 30 Nov 2023 13:36:57 +0300 Subject: [PATCH 054/128] Add current user as member --- group/views.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/group/views.py b/group/views.py index 4530764..dc707d2 100644 --- a/group/views.py +++ b/group/views.py @@ -33,8 +33,10 @@ def get(self, request): def post(self, request): """Create a group""" - data = request.data.copy() - data['creator'] = request.user.id + data = request.data + user_id = request.user.id + data['creator'] = user_id + data['members'] = [user_id] serializer = GroupSerializer(data=data) if serializer.is_valid(): serializer.save() From 7092d76c64f0ddcbbf4d40821764a8e6155158aa Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 30 Nov 2023 13:40:26 +0300 Subject: [PATCH 055/128] Fix lint warnings --- group/serializers.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/group/serializers.py b/group/serializers.py index 3908361..5dc75b3 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -1,19 +1,22 @@ """Serializer module""" from rest_framework import serializers from django.contrib.auth.models import User -from .models import Group from user.serializers import UserSerializer +from .models import Group + class GroupGetSerializer(serializers.ModelSerializer): - """Group seerializer class""" + """Group GET method serializer class""" members = UserSerializer(many=True) + class Meta: """Serializer Meta class""" model = Group fields = ['id', 'name', 'members', 'creator'] + class GroupSerializer(serializers.ModelSerializer): - """Group seerializer class""" + """Group serializer class""" class Meta: """Serializer Meta class""" model = Group From 29e8d81817a0ec4d86dd324178eb012aabf938e5 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 1 Dec 2023 11:08:37 +0300 Subject: [PATCH 056/128] Add migrations to gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 293a1a2..3a82f89 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ __pycache__/ .env -env/ \ No newline at end of file +env/ +**/migrations/* +!**/migrations/__init__.py \ No newline at end of file From 4cdf5532a2e76d0c04056ff8e40eaf5c6f92d389 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 23 Jan 2024 15:10:41 +0300 Subject: [PATCH 057/128] Use one serializer class --- group/serializers.py | 21 ++++++++++++++------- group/views.py | 31 +++++++++++++++++++++++++------ user/serializers.py | 2 +- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/group/serializers.py b/group/serializers.py index 5dc75b3..d83084c 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -5,22 +5,29 @@ from .models import Group -class GroupGetSerializer(serializers.ModelSerializer): - """Group GET method serializer class""" - members = UserSerializer(many=True) +# class GroupGetSerializer(serializers.ModelSerializer): +# """Group GET method serializer class""" +# members = UserSerializer(many=True) - class Meta: - """Serializer Meta class""" - model = Group - fields = ['id', 'name', 'members', 'creator'] +# class Meta: +# """Serializer Meta class""" +# model = Group +# fields = ['id', 'name', 'members', 'creator'] class GroupSerializer(serializers.ModelSerializer): """Group serializer class""" + members = UserSerializer(many=True) # uncommented for serializing nested data + class Meta: """Serializer Meta class""" model = Group fields = ['id', 'name', 'members', 'creator'] + depth = 1 + + # def get_members(self, obj): + # members = UserSerializer(obj.members, many=True, context=self.context).data + # return members def update(self, instance, validated_data): existing_members = instance.members.all() diff --git a/group/views.py b/group/views.py index dc707d2..1109599 100644 --- a/group/views.py +++ b/group/views.py @@ -6,9 +6,11 @@ from rest_framework import status from django.contrib.auth.models import User from django.http import Http404 + +from user.serializers import UserSerializer from .models import Group from .serializers import GroupSerializer -from .serializers import GroupGetSerializer +# from .serializers import GroupGetSerializer class GroupApiView(APIView): @@ -28,21 +30,38 @@ def get_object(self, request, group_id): def get(self, request): """Retrieve a list of all the groups""" groups = Group.objects.all() - serializer = GroupGetSerializer(groups, many=True) + serializer = GroupSerializer(groups, many=True, context={'request': request}) # added request context return Response(serializer.data, status=status.HTTP_200_OK) def post(self, request): """Create a group""" data = request.data - user_id = request.user.id - data['creator'] = user_id - data['members'] = [user_id] + + # user_id = request.user.id + user_serializer = UserSerializer(request.user) + user_data = user_serializer.data + user = request.user + print(f"{user_data} ldldldl") + data['creator'] = user.id + data['members'] = [user_data] serializer = GroupSerializer(data=data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + # def post(self, request): + # """Create a group""" + # data = request.data + # user_id = request.user.id + # data['creator'] = user_id + # data['members'] = [user_id] + # serializer = GroupSerializer(data=data) + # if serializer.is_valid(): + # serializer.save() + # return Response(serializer.data, status=status.HTTP_201_CREATED) + # return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + def patch(self, request): """Update members list""" data = request.data @@ -102,7 +121,7 @@ def get(self, request, group_id): """Retrieve a single group""" try: group = Group.objects.get(id=group_id) - serializer = GroupGetSerializer(group) + serializer = GroupSerializer(group) return Response(serializer.data, status=status.HTTP_200_OK) except Group.DoesNotExist: return Response({"error": "Group not found"}, diff --git a/user/serializers.py b/user/serializers.py index d6f65c6..328d3c3 100644 --- a/user/serializers.py +++ b/user/serializers.py @@ -9,7 +9,7 @@ class UserSerializer(serializers.ModelSerializer): class Meta: """user serializer meta class""" model = User - fields = ['id', 'username', 'password', 'email'] + fields = ['id', 'username', 'email'] #removed password extra_kwargs = {'password': {'write_only': True}} def create(self, validated_data): From 08adc754e3fbd9fd6e4e2f98903fd424e130e6da Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 23 Jan 2024 15:57:20 +0300 Subject: [PATCH 058/128] Remove migration files from gitignore --- .gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 3a82f89..293a1a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ __pycache__/ .env -env/ -**/migrations/* -!**/migrations/__init__.py \ No newline at end of file +env/ \ No newline at end of file From 6d16c818ccf32e611b54876491f37bb682221422 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 24 Jan 2024 09:38:02 +0300 Subject: [PATCH 059/128] Return user object without password in GET method --- group/serializers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/group/serializers.py b/group/serializers.py index d83084c..6d2f8ee 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -23,7 +23,6 @@ class Meta: """Serializer Meta class""" model = Group fields = ['id', 'name', 'members', 'creator'] - depth = 1 # def get_members(self, obj): # members = UserSerializer(obj.members, many=True, context=self.context).data From 5e1cc6a15226ae8ed532a95a3abcb9f5f53aebb5 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 24 Jan 2024 11:10:12 +0300 Subject: [PATCH 060/128] Add password in the user serializer field --- user/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user/serializers.py b/user/serializers.py index 328d3c3..d6f65c6 100644 --- a/user/serializers.py +++ b/user/serializers.py @@ -9,7 +9,7 @@ class UserSerializer(serializers.ModelSerializer): class Meta: """user serializer meta class""" model = User - fields = ['id', 'username', 'email'] #removed password + fields = ['id', 'username', 'password', 'email'] extra_kwargs = {'password': {'write_only': True}} def create(self, validated_data): From 2eaa4a99fc57593e4d4078e4e957e7d698214b8e Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 24 Jan 2024 11:22:06 +0300 Subject: [PATCH 061/128] Create a group with an empty members list --- group/serializers.py | 16 +--------------- group/views.py | 28 ++++------------------------ 2 files changed, 5 insertions(+), 39 deletions(-) diff --git a/group/serializers.py b/group/serializers.py index 6d2f8ee..cb967da 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -5,29 +5,15 @@ from .models import Group -# class GroupGetSerializer(serializers.ModelSerializer): -# """Group GET method serializer class""" -# members = UserSerializer(many=True) - -# class Meta: -# """Serializer Meta class""" -# model = Group -# fields = ['id', 'name', 'members', 'creator'] - - class GroupSerializer(serializers.ModelSerializer): """Group serializer class""" - members = UserSerializer(many=True) # uncommented for serializing nested data + members = UserSerializer(many=True, required=False) class Meta: """Serializer Meta class""" model = Group fields = ['id', 'name', 'members', 'creator'] - # def get_members(self, obj): - # members = UserSerializer(obj.members, many=True, context=self.context).data - # return members - def update(self, instance, validated_data): existing_members = instance.members.all() new_members = validated_data.get("members") diff --git a/group/views.py b/group/views.py index 1109599..f4be89d 100644 --- a/group/views.py +++ b/group/views.py @@ -7,10 +7,8 @@ from django.contrib.auth.models import User from django.http import Http404 -from user.serializers import UserSerializer from .models import Group from .serializers import GroupSerializer -# from .serializers import GroupGetSerializer class GroupApiView(APIView): @@ -30,38 +28,20 @@ def get_object(self, request, group_id): def get(self, request): """Retrieve a list of all the groups""" groups = Group.objects.all() - serializer = GroupSerializer(groups, many=True, context={'request': request}) # added request context + serializer = GroupSerializer(groups, many=True) return Response(serializer.data, status=status.HTTP_200_OK) def post(self, request): """Create a group""" - data = request.data - - # user_id = request.user.id - user_serializer = UserSerializer(request.user) - user_data = user_serializer.data - user = request.user - print(f"{user_data} ldldldl") - data['creator'] = user.id - data['members'] = [user_data] + data = request.data.copy() + user_id = request.user.id + data['creator'] = user_id serializer = GroupSerializer(data=data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - # def post(self, request): - # """Create a group""" - # data = request.data - # user_id = request.user.id - # data['creator'] = user_id - # data['members'] = [user_id] - # serializer = GroupSerializer(data=data) - # if serializer.is_valid(): - # serializer.save() - # return Response(serializer.data, status=status.HTTP_201_CREATED) - # return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - def patch(self, request): """Update members list""" data = request.data From 459847008099093dafc17c8824dcd98434e60adc Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 25 Jan 2024 15:21:27 +0300 Subject: [PATCH 062/128] Change patch error status code to 304 --- group/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/group/views.py b/group/views.py index f4be89d..4740eca 100644 --- a/group/views.py +++ b/group/views.py @@ -66,7 +66,7 @@ def patch(self, request): return Response(serializer.data, status=status.HTTP_200_OK) if errors: return Response({"error": errors}, - status=status.HTTP_404_NOT_FOUND) + status=status.HTTP_304_NOT_MODIFIED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) except PermissionDenied: From 27fb3dc74ec8eb822fc7fae079afc103954a9f45 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 25 Jan 2024 15:47:55 +0300 Subject: [PATCH 063/128] Custom serialize the members field --- group/serializers.py | 7 ++++++- group/views.py | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/group/serializers.py b/group/serializers.py index cb967da..2ca9c1f 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -7,13 +7,18 @@ class GroupSerializer(serializers.ModelSerializer): """Group serializer class""" - members = UserSerializer(many=True, required=False) class Meta: """Serializer Meta class""" model = Group fields = ['id', 'name', 'members', 'creator'] + def to_representation(self, instance): + representation = super().to_representation(instance) + representation['members'] = UserSerializer( + instance.members, many=True).data + return representation + def update(self, instance, validated_data): existing_members = instance.members.all() new_members = validated_data.get("members") diff --git a/group/views.py b/group/views.py index 4740eca..046a832 100644 --- a/group/views.py +++ b/group/views.py @@ -36,6 +36,7 @@ def post(self, request): data = request.data.copy() user_id = request.user.id data['creator'] = user_id + data['members'] = [user_id] serializer = GroupSerializer(data=data) if serializer.is_valid(): serializer.save() From d6f3b7be28d57773abcf81497f6b31844602101d Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 5 Feb 2024 14:09:00 +0300 Subject: [PATCH 064/128] Move members IDs to a variable --- group/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/group/views.py b/group/views.py index 046a832..95d8314 100644 --- a/group/views.py +++ b/group/views.py @@ -47,13 +47,14 @@ def patch(self, request): """Update members list""" data = request.data group_id = data.get("group_id") + members_ids = data.get("members") data.pop('creator', None) data.pop('name', None) try: group = self.get_object(request, group_id) errors = [] - for member in data.get("members"): + for member in members_ids: try: user = User.objects.get(id=member) if user.id not in group.members.all()\ From d971a03dbffade08d2acf437c97587db280034b9 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 5 Feb 2024 14:11:12 +0300 Subject: [PATCH 065/128] Modify error status code --- group/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/group/views.py b/group/views.py index 95d8314..66168d5 100644 --- a/group/views.py +++ b/group/views.py @@ -68,7 +68,7 @@ def patch(self, request): return Response(serializer.data, status=status.HTTP_200_OK) if errors: return Response({"error": errors}, - status=status.HTTP_304_NOT_MODIFIED) + status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) except PermissionDenied: From e3673f0f16683415ba4113ff068187c98c7cf1ff Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 20 Feb 2024 14:49:36 +0300 Subject: [PATCH 066/128] Add pagination to list of groups --- group/views.py | 19 +++++++++++++++---- post_it_backend/settings.py | 4 +++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/group/views.py b/group/views.py index 66168d5..033ba1c 100644 --- a/group/views.py +++ b/group/views.py @@ -4,16 +4,23 @@ from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response from rest_framework import status +from rest_framework.generics import ListAPIView +from rest_framework.pagination import PageNumberPagination + from django.contrib.auth.models import User from django.http import Http404 + from .models import Group from .serializers import GroupSerializer -class GroupApiView(APIView): +class GroupApiView(ListAPIView): """Define methods for performing actions on groups""" permission_classes = [permissions.IsAuthenticated] + queryset = Group.objects.all() + serializer_class = GroupSerializer + pagination_class = PageNumberPagination def get_object(self, request, group_id): """Check for user permission and group existence""" @@ -25,10 +32,14 @@ def get_object(self, request, group_id): except Group.DoesNotExist as exc: raise Http404 from exc - def get(self, request): + def get(self, request, *args, **kwargs): """Retrieve a list of all the groups""" - groups = Group.objects.all() - serializer = GroupSerializer(groups, many=True) + queryset = self.get_queryset() + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.serializer_class(page, many=True) + return self.get_paginated_response(serializer.data) + serializer = self.serializer_class(queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK) def post(self, request): diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py index ce985c9..7afbdc5 100644 --- a/post_it_backend/settings.py +++ b/post_it_backend/settings.py @@ -55,7 +55,9 @@ ], 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', - ] + ], + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 10 } MIDDLEWARE = [ From 345445bcdb0b24c2a210b65cea52adf68ba64fc6 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 21 Feb 2024 11:30:39 +0300 Subject: [PATCH 067/128] Fix arguments-differ pylint warning --- group/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/group/views.py b/group/views.py index 033ba1c..0b27f51 100644 --- a/group/views.py +++ b/group/views.py @@ -22,12 +22,13 @@ class GroupApiView(ListAPIView): serializer_class = GroupSerializer pagination_class = PageNumberPagination - def get_object(self, request, group_id): + def get_object(self, request=None, group_id=None): """Check for user permission and group existence""" try: - group = Group.objects.get(id=group_id) - if request.user != group.creator: - raise PermissionDenied("Access Denied") + if request and group_id is not None: + group = Group.objects.get(id=group_id) + if request.user != group.creator: + raise PermissionDenied("Access Denied") return group except Group.DoesNotExist as exc: raise Http404 from exc From 95d8ff93dadfcd673c3c679ccdf7a72becb57d49 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 22 Feb 2024 01:16:01 +0300 Subject: [PATCH 068/128] Compare using user and creator IDs --- group/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/group/views.py b/group/views.py index 0b27f51..a5de621 100644 --- a/group/views.py +++ b/group/views.py @@ -131,7 +131,7 @@ def delete(self, request, user_id): "error": "Only group creator can remove members"}) user = User.objects.get(id=user_id) - if user == group.creator: + if user.id == group.creator.id: return Response( {"error": "Cannot remove creator from the group"}, status=status.HTTP_403_FORBIDDEN) From 52fccfa485eebfd3dc31babdc73ef9ca30f5eb68 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 22 Feb 2024 02:46:16 +0300 Subject: [PATCH 069/128] Remove duplicate members check --- group/serializers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/group/serializers.py b/group/serializers.py index 2ca9c1f..f0a5621 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -20,12 +20,10 @@ def to_representation(self, instance): return representation def update(self, instance, validated_data): - existing_members = instance.members.all() new_members = validated_data.get("members") for member in new_members: - if member not in existing_members: - user = User.objects.get(id=member) - instance.members.add(user) + user = User.objects.get(id=member.id) + instance.members.add(user) instance.save() return instance From c155adbbc3bce5e765bff21a8b032a4b449ffcfb Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 22 Feb 2024 03:41:46 +0300 Subject: [PATCH 070/128] Add member objects in bulk --- group/serializers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/group/serializers.py b/group/serializers.py index f0a5621..6bf8264 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -22,8 +22,8 @@ def to_representation(self, instance): def update(self, instance, validated_data): new_members = validated_data.get("members") - for member in new_members: - user = User.objects.get(id=member.id) - instance.members.add(user) + member_objects = User.objects.filter( + id__in=[member.id for member in new_members]) + instance.members.add(*member_objects) instance.save() return instance From d7b6c395fa209721235f037a2dd0f32a71ae1ded Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 22 Feb 2024 04:10:25 +0300 Subject: [PATCH 071/128] Remove duplicate check --- group/views.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/group/views.py b/group/views.py index a5de621..ca39f77 100644 --- a/group/views.py +++ b/group/views.py @@ -69,9 +69,7 @@ def patch(self, request): for member in members_ids: try: user = User.objects.get(id=member) - if user.id not in group.members.all()\ - .values_list('id', flat=True): - group.members.add(user) + group.members.add(user) except User.DoesNotExist: errors.append(f"User with ID {member} is not found") serializer = GroupSerializer(group, data=data, partial=True) From 9be1fac16c2f6cb24823cc8f159ac39d15f96859 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 3 Nov 2023 12:31:18 +0300 Subject: [PATCH 072/128] Fix conflicts --- group/migrations/0002_message.py | 26 ++++++++++++++++++++++++++ group/models.py | 11 +++++++++++ group/serializers.py | 9 ++++++++- group/urls.py | 1 + group/views.py | 30 +++++++++++++++++++++++++++++- 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 group/migrations/0002_message.py diff --git a/group/migrations/0002_message.py b/group/migrations/0002_message.py new file mode 100644 index 0000000..d484adf --- /dev/null +++ b/group/migrations/0002_message.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.6 on 2023-11-02 09: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), + ('group', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Message', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('post', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='group.group')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages_authored', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/group/models.py b/group/models.py index 1736aca..04726a6 100644 --- a/group/models.py +++ b/group/models.py @@ -12,3 +12,14 @@ class Group(models.Model): def __str__(self) -> str: return str(self.name) + + +class Message(models.Model): + """A model to define the structure of messages""" + post = models.TextField() + group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name='messages') + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='messages_authored') + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f'{self.user.username}: {self.post}' diff --git a/group/serializers.py b/group/serializers.py index 6bf8264..0a9d18e 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -2,7 +2,7 @@ from rest_framework import serializers from django.contrib.auth.models import User from user.serializers import UserSerializer -from .models import Group +from .models import Group, Message class GroupSerializer(serializers.ModelSerializer): @@ -27,3 +27,10 @@ def update(self, instance, validated_data): instance.members.add(*member_objects) instance.save() return instance + + + +class MessageSerializer(serializers.ModelSerializer): + class Meta: + model = Message + fields = ['id', 'post', 'group', 'user', 'created_at'] \ No newline at end of file diff --git a/group/urls.py b/group/urls.py index 57e123e..4647e20 100644 --- a/group/urls.py +++ b/group/urls.py @@ -7,4 +7,5 @@ path('/', GroupApiView.as_view()), path('detail//', GroupDetailApiView.as_view()), path('user//', GroupDetailApiView.as_view()), + path('/message/', views.post_message), ] diff --git a/group/views.py b/group/views.py index ca39f77..9d35951 100644 --- a/group/views.py +++ b/group/views.py @@ -12,7 +12,7 @@ from .models import Group -from .serializers import GroupSerializer +from .serializers import GroupSerializer, MessageSerializer class GroupApiView(ListAPIView): @@ -147,3 +147,31 @@ def delete(self, request, user_id): except Group.DoesNotExist: return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) + + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def post_message(request, group_id): + """Post message to a group""" + if request.method == 'POST': + try: + group = Group.objects.get(id=group_id) + user = request.user + except Group.DoesNotExist: + return Response({"error":"Group not found"}, status=status.HTTP_404_NOT_FOUND) + + if user in group.members.all(): + data = { + "post": request.data.get("post"), + "group": group.id, + "user":user.id, + } + + serializer = MessageSerializer(data=data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + else: + return Response({"error":"User is not a member of this group"}, status=status.HTTP_403_FORBIDDEN) From 44c6040c810de3c43b4a57022b8a4fc72aa99317 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 23 Feb 2024 14:59:17 +0300 Subject: [PATCH 073/128] Register message model --- group/admin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/group/admin.py b/group/admin.py index b1d0528..5227c5b 100644 --- a/group/admin.py +++ b/group/admin.py @@ -1,5 +1,6 @@ """Register the Group model to see it in Django admin""" from django.contrib import admin -from .models import Group +from .models import Group, Message admin.site.register(Group) +admin.site.register(Message) From ba2e0f5ed90dd4bea4fa9b9f15b09518f7ad0c9f Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 23 Feb 2024 15:01:20 +0300 Subject: [PATCH 074/128] Modify POST method to a class based view --- group/urls.py | 2 +- group/views.py | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/group/urls.py b/group/urls.py index 4647e20..996d592 100644 --- a/group/urls.py +++ b/group/urls.py @@ -7,5 +7,5 @@ path('/', GroupApiView.as_view()), path('detail//', GroupDetailApiView.as_view()), path('user//', GroupDetailApiView.as_view()), - path('/message/', views.post_message), + path('message', GroupDetailApiView.as_view()), ] diff --git a/group/views.py b/group/views.py index 9d35951..8db6fc2 100644 --- a/group/views.py +++ b/group/views.py @@ -149,24 +149,27 @@ def delete(self, request, user_id): status=status.HTTP_404_NOT_FOUND) -@api_view(['POST']) -@permission_classes([IsAuthenticated]) -def post_message(request, group_id): - """Post message to a group""" - if request.method == 'POST': + def post(self, request): + """Post message to a group""" + data = request.data + group_id = data.get("group_id") + post = data.get("post") try: group = Group.objects.get(id=group_id) user = request.user except Group.DoesNotExist: - return Response({"error":"Group not found"}, status=status.HTTP_404_NOT_FOUND) - - if user in group.members.all(): + return Response({"error":"Group not found"}, + status=status.HTTP_404_NOT_FOUND) + except User.DoesNotExist: + return Response({"error": "User not found"}, + status=status.HTTP_404_NOT_FOUND) + if user.id in group.members.all().values_list("id", flat=True): data = { - "post": request.data.get("post"), + "post": post, "group": group.id, "user":user.id, } - + serializer = MessageSerializer(data=data) if serializer.is_valid(): serializer.save() From 0fdbbbc8e46211bfcb7d88403d83ab9d63d7320d Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 23 Feb 2024 15:10:48 +0300 Subject: [PATCH 075/128] Fix lint warnings --- group/models.py | 6 ++++-- group/serializers.py | 5 +++-- group/views.py | 13 ++++++------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/group/models.py b/group/models.py index 04726a6..bc056ac 100644 --- a/group/models.py +++ b/group/models.py @@ -17,8 +17,10 @@ def __str__(self) -> str: class Message(models.Model): """A model to define the structure of messages""" post = models.TextField() - group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name='messages') - user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='messages_authored') + group = models.ForeignKey(Group, on_delete=models.CASCADE, + related_name='messages') + user = models.ForeignKey(User, on_delete=models.CASCADE, + related_name='messages_authored') created_at = models.DateTimeField(auto_now_add=True) def __str__(self): diff --git a/group/serializers.py b/group/serializers.py index 0a9d18e..a999a1b 100644 --- a/group/serializers.py +++ b/group/serializers.py @@ -29,8 +29,9 @@ def update(self, instance, validated_data): return instance - class MessageSerializer(serializers.ModelSerializer): + """Message serializer class""" class Meta: + """Message serializer Meta class""" model = Message - fields = ['id', 'post', 'group', 'user', 'created_at'] \ No newline at end of file + fields = ['id', 'post', 'group', 'user', 'created_at'] diff --git a/group/views.py b/group/views.py index 8db6fc2..5b9ea55 100644 --- a/group/views.py +++ b/group/views.py @@ -147,7 +147,6 @@ def delete(self, request, user_id): except Group.DoesNotExist: return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) - def post(self, request): """Post message to a group""" @@ -158,7 +157,7 @@ def post(self, request): group = Group.objects.get(id=group_id) user = request.user except Group.DoesNotExist: - return Response({"error":"Group not found"}, + return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) except User.DoesNotExist: return Response({"error": "User not found"}, @@ -167,14 +166,14 @@ def post(self, request): data = { "post": post, "group": group.id, - "user":user.id, + "user": user.id, } serializer = MessageSerializer(data=data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - else: - return Response({"error":"User is not a member of this group"}, status=status.HTTP_403_FORBIDDEN) + return Response(serializer.errors, + status=status.HTTP_400_BAD_REQUEST) + return Response({"error": "User is not a member of this group"}, + status=status.HTTP_403_FORBIDDEN) From f40ba3162ff2e65bbd2d82a1f7dcf8adb0955862 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 26 Feb 2024 15:05:09 +0300 Subject: [PATCH 076/128] Move message post method to separate view class --- group/urls.py | 4 ++-- group/views.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/group/urls.py b/group/urls.py index 996d592..7610d51 100644 --- a/group/urls.py +++ b/group/urls.py @@ -1,11 +1,11 @@ """urls module""" from django.urls import path -from .views import GroupApiView, GroupDetailApiView +from .views import GroupApiView, GroupDetailApiView, MessageAPIView urlpatterns = [ path('', GroupApiView.as_view()), path('/', GroupApiView.as_view()), path('detail//', GroupDetailApiView.as_view()), path('user//', GroupDetailApiView.as_view()), - path('message', GroupDetailApiView.as_view()), + path('message', MessageAPIView.as_view()), ] diff --git a/group/views.py b/group/views.py index 5b9ea55..00f9da9 100644 --- a/group/views.py +++ b/group/views.py @@ -148,6 +148,11 @@ def delete(self, request, user_id): return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) + +class MessageAPIView(APIView): + """Define methods for messaging within the group""" + permission_classes = [permissions.IsAuthenticated] + def post(self, request): """Post message to a group""" data = request.data From 640d86be669851b3eb7cf6c7e03ac4a17e7e8f75 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 27 Feb 2024 10:34:08 +0300 Subject: [PATCH 077/128] Modify methods in user to class based views --- user/urls.py | 6 +++--- user/views.py | 23 ++++++++++------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/user/urls.py b/user/urls.py index 0c41c75..dc96cfc 100644 --- a/user/urls.py +++ b/user/urls.py @@ -1,9 +1,9 @@ """user urls module""" from django.urls import path -from . import views +from .views import UserSignupAPIView, UserSigninAPIView urlpatterns = [ - path('signup/', views.create_user), - path('signin/', views.signin_user), + path('signup/', UserSignupAPIView.as_view()), + path('signin/', UserSigninAPIView.as_view()), ] diff --git a/user/views.py b/user/views.py index bb3d1ea..6ae6ee7 100644 --- a/user/views.py +++ b/user/views.py @@ -1,31 +1,29 @@ """user views module""" -from rest_framework.decorators import api_view +from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.authtoken.models import Token from django.contrib.auth import authenticate from .serializers import UserSerializer -# Create your views here. -@api_view(['POST']) -def create_user(request): - """a function for signing up a user""" - if request.method == 'POST': +class UserSignupAPIView(APIView): + """Define method(s) for signing up""" + def post(self, request): + """a function for signing up a user""" serializer = UserSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) - return None + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) -@api_view(['POST']) -def signin_user(request): - """a function for signing in a user""" - if request.method == 'POST': +class UserSigninAPIView(APIView): + """Define method(s) for signing in a user""" + def post(self, request): + """a function for signing in a user""" username = request.data.get('username') password = request.data.get('password') - user = authenticate(username=username, password=password) if not user: return Response({ @@ -35,4 +33,3 @@ def signin_user(request): return Response({ 'token': token.key}, status=status.HTTP_200_OK) - return None From d28382b8ca0c3535a51203a708ecb3cf3558e6c3 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 2 Nov 2023 12:26:53 +0300 Subject: [PATCH 078/128] Create api to add users to a group --- group/urls.py | 3 ++- group/views.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/group/urls.py b/group/urls.py index 7610d51..0b8536b 100644 --- a/group/urls.py +++ b/group/urls.py @@ -8,4 +8,5 @@ path('detail//', GroupDetailApiView.as_view()), path('user//', GroupDetailApiView.as_view()), path('message', MessageAPIView.as_view()), -] + path('/user/', views.add_users), +] \ No newline at end of file diff --git a/group/views.py b/group/views.py index 00f9da9..8d6f452 100644 --- a/group/views.py +++ b/group/views.py @@ -182,3 +182,31 @@ def post(self, request): status=status.HTTP_400_BAD_REQUEST) return Response({"error": "User is not a member of this group"}, status=status.HTTP_403_FORBIDDEN) + + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def add_users(request, group_id): + """Add user(s) to a group""" + if request.method == 'POST': + members = request.data.get('members') + try: + group = Group.objects.get(id=group_id) + except Group.DoesNotExist: + return Response({"error":"Group not found"}, status=status.HTTP_404_NOT_FOUND) + + for member in group.members.all(): + if member.id not in members: + members.append(member.id) + + for member in members: + user = User.objects.get(id=member) + if user.id not in group.members.all().values_list('id', flat=True): + group.members.add(user) + + serializer = GroupSerializer(group, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) From 34a710884cdabc7e8741da5e2849dd206de3001b Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 3 Nov 2023 12:31:18 +0300 Subject: [PATCH 079/128] Create API to post messages to group --- group/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/group/urls.py b/group/urls.py index 0b8536b..ee13b1b 100644 --- a/group/urls.py +++ b/group/urls.py @@ -9,4 +9,5 @@ path('user//', GroupDetailApiView.as_view()), path('message', MessageAPIView.as_view()), path('/user/', views.add_users), + path('/message/', views.post_message), ] \ No newline at end of file From 46cd67806f1e1ea6d9f60299c3eab537628be3c2 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 3 Nov 2023 13:53:33 +0300 Subject: [PATCH 080/128] Create API to retrieve messages in a group --- group/urls.py | 1 + group/views.py | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/group/urls.py b/group/urls.py index ee13b1b..1f5b5f1 100644 --- a/group/urls.py +++ b/group/urls.py @@ -10,4 +10,5 @@ path('message', MessageAPIView.as_view()), path('/user/', views.add_users), path('/message/', views.post_message), + path('/messages/', views.retrieve_messages), ] \ No newline at end of file diff --git a/group/views.py b/group/views.py index 8d6f452..9e1ccf4 100644 --- a/group/views.py +++ b/group/views.py @@ -11,7 +11,7 @@ from django.http import Http404 -from .models import Group +from .models import Group, Message from .serializers import GroupSerializer, MessageSerializer @@ -210,3 +210,25 @@ def add_users(request, group_id): return Response(serializer.data, status=status.HTTP_200_OK) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + + + + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def retrieve_messages(request, group_id): + if request.method == 'GET': + try: + group = Group.objects.get(id=group_id) + user = request.user + except Group.DoesNotExist: + return Response({"error":"Group not found"}, status=status.HTTP_404_NOT_FOUND) + + if user in group.members.all(): + messages = Message.objects.filter(group=group) + + serializer = MessageSerializer(messages, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + else: + return Response({"error":"User is not a member of this group"}, status=status.HTTP_403_FORBIDDEN) From 55842ad97045e56f5e024498faff1e643d507297 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 27 Feb 2024 11:34:38 +0300 Subject: [PATCH 081/128] Modify get method to class based view --- group/urls.py | 6 ++---- group/views.py | 54 ++++++++++++-------------------------------------- 2 files changed, 15 insertions(+), 45 deletions(-) diff --git a/group/urls.py b/group/urls.py index 1f5b5f1..cf9e689 100644 --- a/group/urls.py +++ b/group/urls.py @@ -8,7 +8,5 @@ path('detail//', GroupDetailApiView.as_view()), path('user//', GroupDetailApiView.as_view()), path('message', MessageAPIView.as_view()), - path('/user/', views.add_users), - path('/message/', views.post_message), - path('/messages/', views.retrieve_messages), -] \ No newline at end of file + path('messages', MessageAPIView.as_view()), +] diff --git a/group/views.py b/group/views.py index 9e1ccf4..ab7dc3e 100644 --- a/group/views.py +++ b/group/views.py @@ -183,52 +183,24 @@ def post(self, request): return Response({"error": "User is not a member of this group"}, status=status.HTTP_403_FORBIDDEN) - -@api_view(['POST']) -@permission_classes([IsAuthenticated]) -def add_users(request, group_id): - """Add user(s) to a group""" - if request.method == 'POST': - members = request.data.get('members') - try: - group = Group.objects.get(id=group_id) - except Group.DoesNotExist: - return Response({"error":"Group not found"}, status=status.HTTP_404_NOT_FOUND) - - for member in group.members.all(): - if member.id not in members: - members.append(member.id) - - for member in members: - user = User.objects.get(id=member) - if user.id not in group.members.all().values_list('id', flat=True): - group.members.add(user) - - serializer = GroupSerializer(group, data=request.data, partial=True) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status=status.HTTP_200_OK) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - - - - -@api_view(['GET']) -@permission_classes([IsAuthenticated]) -def retrieve_messages(request, group_id): - if request.method == 'GET': + def get(self, request): + """Retrieve all messages in a group""" + data = request.data + group_id = data.get("group_id") try: group = Group.objects.get(id=group_id) user = request.user except Group.DoesNotExist: - return Response({"error":"Group not found"}, status=status.HTTP_404_NOT_FOUND) - - if user in group.members.all(): + return Response({"error": "Group not found"}, + status=status.HTTP_404_NOT_FOUND) + except User.DoesNotExist: + return Response({"error": "User not found"}, + status=status.HTTP_404_NOT_FOUND) + + if user.id in group.members.all().values_list("id", flat=True): messages = Message.objects.filter(group=group) serializer = MessageSerializer(messages, many=True) return Response(serializer.data, status=status.HTTP_200_OK) - else: - return Response({"error":"User is not a member of this group"}, status=status.HTTP_403_FORBIDDEN) + return Response({"error": "User is not a member of this group"}, + status=status.HTTP_403_FORBIDDEN) From f48a7b3e52c5dafb0042ce74222c76b001761508 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 27 Feb 2024 14:42:16 +0300 Subject: [PATCH 082/128] Modify resource to use plural form --- api/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/urls.py b/api/urls.py index b0b5646..3e4aaf1 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,6 +1,6 @@ from django.urls import path, include urlpatterns = [ - path('user/', include('user.urls')), - path('group/', include('group.urls')), + path('users/', include('user.urls')), + path('groups/', include('group.urls')), ] \ No newline at end of file From 11e82d664c48ced018cf5df99d33372c586e6e49 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 27 Feb 2024 18:21:28 +0300 Subject: [PATCH 083/128] Modify message url --- group/urls.py | 4 ++-- group/views.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/group/urls.py b/group/urls.py index cf9e689..cb24cf0 100644 --- a/group/urls.py +++ b/group/urls.py @@ -6,7 +6,7 @@ path('', GroupApiView.as_view()), path('/', GroupApiView.as_view()), path('detail//', GroupDetailApiView.as_view()), - path('user//', GroupDetailApiView.as_view()), - path('message', MessageAPIView.as_view()), + path('users//', GroupDetailApiView.as_view()), path('messages', MessageAPIView.as_view()), + path('messages//', MessageAPIView.as_view()), ] diff --git a/group/views.py b/group/views.py index ab7dc3e..b2fab7f 100644 --- a/group/views.py +++ b/group/views.py @@ -183,13 +183,14 @@ def post(self, request): return Response({"error": "User is not a member of this group"}, status=status.HTTP_403_FORBIDDEN) - def get(self, request): + def get(self, request, user_id): """Retrieve all messages in a group""" data = request.data group_id = data.get("group_id") try: group = Group.objects.get(id=group_id) - user = request.user + user = User.objects.get(id=user_id) + user_id = user.id except Group.DoesNotExist: return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) @@ -197,7 +198,7 @@ def get(self, request): return Response({"error": "User not found"}, status=status.HTTP_404_NOT_FOUND) - if user.id in group.members.all().values_list("id", flat=True): + if user_id in group.members.all().values_list("id", flat=True): messages = Message.objects.filter(group=group) serializer = MessageSerializer(messages, many=True) From bf592b42eecc8d69d6c9c24a4217069bf9365155 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 27 Feb 2024 14:37:14 +0300 Subject: [PATCH 084/128] Test signup api --- post_it_backend/settings.py | 3 +++ pytest.ini | 3 +++ requirements.txt | 4 +++- user/tests.py | 1 - user/tests/__init__.py | 0 user/tests/conftest.py | 20 ++++++++++++++++++++ user/tests/test_users_api.py | 19 +++++++++++++++++++ user/urls.py | 2 +- 8 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 pytest.ini delete mode 100644 user/tests.py create mode 100644 user/tests/__init__.py create mode 100644 user/tests/conftest.py create mode 100644 user/tests/test_users_api.py diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py index 7afbdc5..56e4f6d 100644 --- a/post_it_backend/settings.py +++ b/post_it_backend/settings.py @@ -102,6 +102,9 @@ 'PASSWORD': env('DB_PASSWORD'), 'HOST': env('DB_HOST'), 'PORT': env('DB_PORT'), + 'TEST': { + 'NAME': 'test_postgres' + } } } diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..5aec779 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +DJANGO_SETTINGS_MODULE = post_it_backend.settings +python_files = tests.py test_*.py *_tests.py \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d34b032..e89b2ce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,6 @@ djangorestframework==3.14.0 flake8==6.1.0 psycopg2==2.9.9 pylint==3.0.2 -pylint-django==2.5.5 \ No newline at end of file +pylint-django==2.5.5 +pytest==8.0.2 +pytest-django==4.8.0 \ No newline at end of file diff --git a/user/tests.py b/user/tests.py deleted file mode 100644 index a39b155..0000000 --- a/user/tests.py +++ /dev/null @@ -1 +0,0 @@ -# Create your tests here. diff --git a/user/tests/__init__.py b/user/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/tests/conftest.py b/user/tests/conftest.py new file mode 100644 index 0000000..26d1f55 --- /dev/null +++ b/user/tests/conftest.py @@ -0,0 +1,20 @@ +# import pytest +# from django.conf import settings +# # from rest_framework.test import APIClient + +# @pytest.fixture(scope="session") +# def test_db_setup(): +# settings.DATABASE['default'] = { +# 'ENGINE': 'django.db.backends.postgresql', +# 'HOST': 'db', +# 'NAME': 'test_postgres', +# 'USER': 'test', +# 'PASSWORD': 'testuser' +# } +# # @pytest.fixture(scope="function") +# # def api_client() -> APIClient: +# # """ +# # Provide APIClient +# # :return: APIClient +# # """ +# # yield APIClient() \ No newline at end of file diff --git a/user/tests/test_users_api.py b/user/tests/test_users_api.py new file mode 100644 index 0000000..46c833a --- /dev/null +++ b/user/tests/test_users_api.py @@ -0,0 +1,19 @@ +import pytest +from django.urls import reverse + +@pytest.mark.django_db +def test_user_signup_api(client): + """ + Test the user signup API + :param api_client + :return None + """ + url = reverse('signup') + payload = { + "username":"testuser", + "password":"123", + "email":"testuser@gmail.com" + } + response = client.post(url, data=payload, format="json") + assert response.status_code == 201 + assert response.data["username"] == payload["username"] \ No newline at end of file diff --git a/user/urls.py b/user/urls.py index dc96cfc..08eea8a 100644 --- a/user/urls.py +++ b/user/urls.py @@ -4,6 +4,6 @@ urlpatterns = [ - path('signup/', UserSignupAPIView.as_view()), + path('signup/', UserSignupAPIView.as_view(), name='signup'), path('signin/', UserSigninAPIView.as_view()), ] From 30c3c1b66bfed24fa95e07450d02ffef49aa516d Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 29 Feb 2024 17:26:05 +0300 Subject: [PATCH 085/128] Optimise query --- group/views.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/group/views.py b/group/views.py index b2fab7f..962e64f 100644 --- a/group/views.py +++ b/group/views.py @@ -159,7 +159,7 @@ def post(self, request): group_id = data.get("group_id") post = data.get("post") try: - group = Group.objects.get(id=group_id) + group = Group.objects.select_related("creator").get(id=group_id) user = request.user except Group.DoesNotExist: return Response({"error": "Group not found"}, @@ -188,9 +188,8 @@ def get(self, request, user_id): data = request.data group_id = data.get("group_id") try: - group = Group.objects.get(id=group_id) - user = User.objects.get(id=user_id) - user_id = user.id + group = Group.objects.select_related("creator").get(id=group_id) + user = User.objects.select_related("auth_token").get(id=user_id) except Group.DoesNotExist: return Response({"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND) @@ -198,7 +197,7 @@ def get(self, request, user_id): return Response({"error": "User not found"}, status=status.HTTP_404_NOT_FOUND) - if user_id in group.members.all().values_list("id", flat=True): + if user.id in group.members.all().values_list("id", flat=True): messages = Message.objects.filter(group=group) serializer = MessageSerializer(messages, many=True) From df645e7711f0982a7a864202c7353f5edc798edc Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Fri, 1 Mar 2024 12:42:57 +0300 Subject: [PATCH 086/128] Add distinct URL patterns for POST and GET --- group/urls.py | 2 +- group/views.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/group/urls.py b/group/urls.py index cb24cf0..d29ba61 100644 --- a/group/urls.py +++ b/group/urls.py @@ -8,5 +8,5 @@ path('detail//', GroupDetailApiView.as_view()), path('users//', GroupDetailApiView.as_view()), path('messages', MessageAPIView.as_view()), - path('messages//', MessageAPIView.as_view()), + path('/messages//', MessageAPIView.as_view()), ] diff --git a/group/views.py b/group/views.py index 962e64f..245b51b 100644 --- a/group/views.py +++ b/group/views.py @@ -153,7 +153,7 @@ class MessageAPIView(APIView): """Define methods for messaging within the group""" permission_classes = [permissions.IsAuthenticated] - def post(self, request): + def post(self, request, *args, **kwargs): """Post message to a group""" data = request.data group_id = data.get("group_id") @@ -183,10 +183,10 @@ def post(self, request): return Response({"error": "User is not a member of this group"}, status=status.HTTP_403_FORBIDDEN) - def get(self, request, user_id): + def get(self, request, *args, **kwargs): """Retrieve all messages in a group""" - data = request.data - group_id = data.get("group_id") + group_id = kwargs.get("group_id") + user_id = kwargs.get("user_id") try: group = Group.objects.select_related("creator").get(id=group_id) user = User.objects.select_related("auth_token").get(id=user_id) From 5d029fd9c33a6bd1cf0034887ce82393f287e889 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 4 Mar 2024 13:09:36 +0300 Subject: [PATCH 087/128] Remove conftest file --- user/tests/conftest.py | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 user/tests/conftest.py diff --git a/user/tests/conftest.py b/user/tests/conftest.py deleted file mode 100644 index 26d1f55..0000000 --- a/user/tests/conftest.py +++ /dev/null @@ -1,20 +0,0 @@ -# import pytest -# from django.conf import settings -# # from rest_framework.test import APIClient - -# @pytest.fixture(scope="session") -# def test_db_setup(): -# settings.DATABASE['default'] = { -# 'ENGINE': 'django.db.backends.postgresql', -# 'HOST': 'db', -# 'NAME': 'test_postgres', -# 'USER': 'test', -# 'PASSWORD': 'testuser' -# } -# # @pytest.fixture(scope="function") -# # def api_client() -> APIClient: -# # """ -# # Provide APIClient -# # :return: APIClient -# # """ -# # yield APIClient() \ No newline at end of file From 495c4b48716d8dec1d918e08212ef91c4d18fc6c Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 4 Mar 2024 13:10:43 +0300 Subject: [PATCH 088/128] Test user signin api --- user/tests/test_users_api.py | 19 ++++++++++++++++--- user/urls.py | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/user/tests/test_users_api.py b/user/tests/test_users_api.py index 46c833a..6e4276b 100644 --- a/user/tests/test_users_api.py +++ b/user/tests/test_users_api.py @@ -1,13 +1,15 @@ import pytest from django.urls import reverse +from django.contrib.auth.models import User @pytest.mark.django_db def test_user_signup_api(client): """ - Test the user signup API - :param api_client + Test the user signup and signin APIs + :param client :return None """ + url = reverse('signup') payload = { "username":"testuser", @@ -16,4 +18,15 @@ def test_user_signup_api(client): } response = client.post(url, data=payload, format="json") assert response.status_code == 201 - assert response.data["username"] == payload["username"] \ No newline at end of file + + user = User.objects.get(username="testuser") + assert user.username == "testuser" + + #user signin + url_signin = reverse('signin') + data_signin = { + "username":"testuser", + "password":"123" + } + response_signin = client.post(url_signin, data=data_signin, format="json") + assert response_signin.status_code == 200 diff --git a/user/urls.py b/user/urls.py index 08eea8a..4e41412 100644 --- a/user/urls.py +++ b/user/urls.py @@ -5,5 +5,5 @@ urlpatterns = [ path('signup/', UserSignupAPIView.as_view(), name='signup'), - path('signin/', UserSigninAPIView.as_view()), + path('signin/', UserSigninAPIView.as_view(), name='signin'), ] From a3b12806b6ad337a995692e65f3632cd314908a5 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 5 Mar 2024 00:16:27 +0300 Subject: [PATCH 089/128] Remove pytest --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e89b2ce..3b3c51e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,4 @@ flake8==6.1.0 psycopg2==2.9.9 pylint==3.0.2 pylint-django==2.5.5 -pytest==8.0.2 pytest-django==4.8.0 \ No newline at end of file From bf05325de894d6319c84945744fbb1906152fd4b Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 5 Mar 2024 01:04:44 +0300 Subject: [PATCH 090/128] Separate test for signup and signin --- user/tests/test_users_api.py | 38 +++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/user/tests/test_users_api.py b/user/tests/test_users_api.py index 6e4276b..f7ea335 100644 --- a/user/tests/test_users_api.py +++ b/user/tests/test_users_api.py @@ -1,32 +1,52 @@ import pytest from django.urls import reverse from django.contrib.auth.models import User +from rest_framework import status + +@pytest.fixture +def create_user(db): + """ + Create a user object + :param db + :return None + """ + def make_user(username, password, email): + User.objects.create_user(username=username, password=password, email=email) + return make_user @pytest.mark.django_db def test_user_signup_api(client): """ - Test the user signup and signin APIs + Test the user signup API :param client :return None """ url = reverse('signup') - payload = { + data = { "username":"testuser", "password":"123", "email":"testuser@gmail.com" } - response = client.post(url, data=payload, format="json") - assert response.status_code == 201 + response = client.post(url, data=data, format="json") + assert response.status_code == status.HTTP_201_CREATED user = User.objects.get(username="testuser") assert user.username == "testuser" - #user signin - url_signin = reverse('signin') - data_signin = { +@pytest.mark.django_db +def test_user_signin_api(client, create_user): + """ + Test the user signin API + :param client, create_user + :return None + """ + create_user("testuser", "123", "testuser@gmail.com") + url = reverse('signin') + data = { "username":"testuser", "password":"123" } - response_signin = client.post(url_signin, data=data_signin, format="json") - assert response_signin.status_code == 200 + + response = client.post(url, data=data, format="json") + assert response.status_code == status.HTTP_200_OK From f7b37d257be26279a6feeb7d2e005e9b252c20a4 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 18 Mar 2024 13:03:02 +0300 Subject: [PATCH 091/128] Create separate detail class --- group/urls.py | 9 +++++++-- group/views.py | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/group/urls.py b/group/urls.py index d29ba61..5900772 100644 --- a/group/urls.py +++ b/group/urls.py @@ -1,6 +1,10 @@ """urls module""" from django.urls import path -from .views import GroupApiView, GroupDetailApiView, MessageAPIView +from .views import\ + GroupApiView, \ + GroupDetailApiView, \ + MessageAPIView, \ + MessageDetailAPIView urlpatterns = [ path('', GroupApiView.as_view()), @@ -8,5 +12,6 @@ path('detail//', GroupDetailApiView.as_view()), path('users//', GroupDetailApiView.as_view()), path('messages', MessageAPIView.as_view()), - path('/messages//', MessageAPIView.as_view()), + path('/messages//', + MessageDetailAPIView.as_view()), ] diff --git a/group/views.py b/group/views.py index 245b51b..0d7eadd 100644 --- a/group/views.py +++ b/group/views.py @@ -183,6 +183,11 @@ def post(self, request, *args, **kwargs): return Response({"error": "User is not a member of this group"}, status=status.HTTP_403_FORBIDDEN) + +class MessageDetailAPIView(APIView): + """Define methods for messaging within the group""" + permission_classes = [permissions.IsAuthenticated] + def get(self, request, *args, **kwargs): """Retrieve all messages in a group""" group_id = kwargs.get("group_id") From 5699b2c93185a3613a3c08b57a634035fecef1de Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 12 Mar 2024 18:33:16 +0300 Subject: [PATCH 092/128] Add API documentation using swagger --- post_it_backend/settings.py | 1 + post_it_backend/urls.py | 22 ++++++++++++++++++++++ requirements.txt | 3 ++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/post_it_backend/settings.py b/post_it_backend/settings.py index 56e4f6d..c7c7f01 100644 --- a/post_it_backend/settings.py +++ b/post_it_backend/settings.py @@ -47,6 +47,7 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'drf_yasg', ] REST_FRAMEWORK = { diff --git a/post_it_backend/urls.py b/post_it_backend/urls.py index b59b8e9..3b383ff 100644 --- a/post_it_backend/urls.py +++ b/post_it_backend/urls.py @@ -16,8 +16,30 @@ """ from django.contrib import admin from django.urls import path, include +from drf_yasg.views import get_schema_view +from drf_yasg import openapi +from rest_framework import permissions + + +schema_view = get_schema_view( + openapi.Info( + title='POST IT APIs', + default_version='v1', + description='API documentation', + terms_of_service='', + contact=openapi.Contact(email=''), + license=openapi.License(name='BSD Licence'), + ), + public=True, + permission_classes=(permissions.AllowAny,), +) urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('api.urls')), + + #DRF URLs + path('swagger/', schema_view.without_ui(cache_timeout=0), name='schema-json'), + path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), + path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc') ] diff --git a/requirements.txt b/requirements.txt index 3b3c51e..8222ed0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ flake8==6.1.0 psycopg2==2.9.9 pylint==3.0.2 pylint-django==2.5.5 -pytest-django==4.8.0 \ No newline at end of file +pytest-django==4.8.0 +drf-yasg==1.21.7 \ No newline at end of file From 2675ae394046654effbc86ea62ea860a6a6fc699 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 25 Mar 2024 15:55:11 +0300 Subject: [PATCH 093/128] Modify path name --- post_it_backend/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/post_it_backend/urls.py b/post_it_backend/urls.py index 3b383ff..64c0b71 100644 --- a/post_it_backend/urls.py +++ b/post_it_backend/urls.py @@ -39,7 +39,7 @@ path('api/', include('api.urls')), #DRF URLs - path('swagger/', schema_view.without_ui(cache_timeout=0), name='schema-json'), - path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), + path('documentation/', schema_view.without_ui(cache_timeout=0), name='schema-json'), + path('documentation/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc') ] From c2fb6a17ad07155fd1dcc352a441cb26b3451c7f Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 5 Mar 2024 00:02:13 +0300 Subject: [PATCH 094/128] Test create group api --- group/tests/__init__.py | 0 group/tests/test_groups_api.py | 18 ++++++++++++++++++ group/urls.py | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 group/tests/__init__.py create mode 100644 group/tests/test_groups_api.py diff --git a/group/tests/__init__.py b/group/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/group/tests/test_groups_api.py b/group/tests/test_groups_api.py new file mode 100644 index 0000000..9e497b7 --- /dev/null +++ b/group/tests/test_groups_api.py @@ -0,0 +1,18 @@ +import pytest +from django.urls import reverse + +@pytest.mark.django_db +def test_groups_api(client): + """ + Test groups api endpoints + :param client + :return None + """ + + url = reverse("group") + data = { + "name":"test group" + } + + response_create = client.post(url, data=data, format="json") + assert response_create.status_code == 201 \ No newline at end of file diff --git a/group/urls.py b/group/urls.py index 5900772..083c474 100644 --- a/group/urls.py +++ b/group/urls.py @@ -7,7 +7,7 @@ MessageDetailAPIView urlpatterns = [ - path('', GroupApiView.as_view()), + path('', GroupApiView.as_view(), name='group'), path('/', GroupApiView.as_view()), path('detail//', GroupDetailApiView.as_view()), path('users//', GroupDetailApiView.as_view()), From 6df8224e58ecb1a1f323bcc8ed5b0e0f353b5800 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 5 Mar 2024 02:51:42 +0300 Subject: [PATCH 095/128] Modify function name for test create group --- group/tests/test_groups_api.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/group/tests/test_groups_api.py b/group/tests/test_groups_api.py index 9e497b7..19f76a9 100644 --- a/group/tests/test_groups_api.py +++ b/group/tests/test_groups_api.py @@ -1,8 +1,9 @@ import pytest from django.urls import reverse +from rest_framework import status @pytest.mark.django_db -def test_groups_api(client): +def test_create_group_api(client): """ Test groups api endpoints :param client @@ -14,5 +15,5 @@ def test_groups_api(client): "name":"test group" } - response_create = client.post(url, data=data, format="json") - assert response_create.status_code == 201 \ No newline at end of file + response = client.post(url, data=data, format="json") + assert response.status_code == status.HTTP_201_CREATED \ No newline at end of file From 2ea75f998af08c9078ef4e5c00ae411db6e15d40 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 5 Mar 2024 15:40:06 +0300 Subject: [PATCH 096/128] Restructure test files --- group/tests/__init__.py | 0 group/tests/test_groups_api.py | 19 ------------- user/tests/__init__.py | 0 user/tests/test_users_api.py | 52 ---------------------------------- 4 files changed, 71 deletions(-) delete mode 100644 group/tests/__init__.py delete mode 100644 group/tests/test_groups_api.py delete mode 100644 user/tests/__init__.py delete mode 100644 user/tests/test_users_api.py diff --git a/group/tests/__init__.py b/group/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/group/tests/test_groups_api.py b/group/tests/test_groups_api.py deleted file mode 100644 index 19f76a9..0000000 --- a/group/tests/test_groups_api.py +++ /dev/null @@ -1,19 +0,0 @@ -import pytest -from django.urls import reverse -from rest_framework import status - -@pytest.mark.django_db -def test_create_group_api(client): - """ - Test groups api endpoints - :param client - :return None - """ - - url = reverse("group") - data = { - "name":"test group" - } - - response = client.post(url, data=data, format="json") - assert response.status_code == status.HTTP_201_CREATED \ No newline at end of file diff --git a/user/tests/__init__.py b/user/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/user/tests/test_users_api.py b/user/tests/test_users_api.py deleted file mode 100644 index f7ea335..0000000 --- a/user/tests/test_users_api.py +++ /dev/null @@ -1,52 +0,0 @@ -import pytest -from django.urls import reverse -from django.contrib.auth.models import User -from rest_framework import status - -@pytest.fixture -def create_user(db): - """ - Create a user object - :param db - :return None - """ - def make_user(username, password, email): - User.objects.create_user(username=username, password=password, email=email) - return make_user - -@pytest.mark.django_db -def test_user_signup_api(client): - """ - Test the user signup API - :param client - :return None - """ - - url = reverse('signup') - data = { - "username":"testuser", - "password":"123", - "email":"testuser@gmail.com" - } - response = client.post(url, data=data, format="json") - assert response.status_code == status.HTTP_201_CREATED - - user = User.objects.get(username="testuser") - assert user.username == "testuser" - -@pytest.mark.django_db -def test_user_signin_api(client, create_user): - """ - Test the user signin API - :param client, create_user - :return None - """ - create_user("testuser", "123", "testuser@gmail.com") - url = reverse('signin') - data = { - "username":"testuser", - "password":"123" - } - - response = client.post(url, data=data, format="json") - assert response.status_code == status.HTTP_200_OK From f18a63824bc183bd62d2e8b8fe1ff6b293476605 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 5 Mar 2024 15:41:29 +0300 Subject: [PATCH 097/128] Create conftest file and add reusable test functions --- tests/__init__.py | 0 tests/conftest.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..1aad3f5 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,33 @@ +import pytest +from django.contrib.auth.models import User +from rest_framework.test import APIClient +from django.urls import reverse + +@pytest.fixture +def api_client(): + return APIClient() + +@pytest.fixture +def create_user(db): + """ + Create a user object + :param db + :return None + """ + def make_user(username, password, email): + User.objects.create_user(username=username, password=password, email=email) + return make_user + +@pytest.fixture +def authenticate_user_with_token(db, api_client, create_user): + def authenticate_user(): + create_user("testuser", "123", "testuser@gmail.com") + url = reverse('signin') + data = { + "username":"testuser", + "password":"123" + } + + response = api_client.post(url, data=data, format="json") + return response.data["token"] + return authenticate_user From ee10814ef545ef6aa90ba6e95e12786f889fdae9 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 5 Mar 2024 15:42:46 +0300 Subject: [PATCH 098/128] Move tests for users api to root test folder --- tests/test_users_api.py | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/test_users_api.py diff --git a/tests/test_users_api.py b/tests/test_users_api.py new file mode 100644 index 0000000..3d4d50e --- /dev/null +++ b/tests/test_users_api.py @@ -0,0 +1,42 @@ +import pytest +from django.urls import reverse +from django.contrib.auth.models import User +from rest_framework import status + +@pytest.mark.django_db +def test_user_signup_api(api_client): + """ + Test the user signup API + :param client + :return None + """ + + url = reverse('signup') + data = { + "username":"testuser", + "password":"123", + "email":"testuser@gmail.com" + } + response = api_client.post(url, data=data, format="json") + assert response.status_code == status.HTTP_201_CREATED + + user = User.objects.get(username="testuser") + assert user.username == "testuser" + +@pytest.mark.django_db +def test_user_signin_api(api_client, create_user): + """ + Test the user signin API + :param client, create_user + :return None + """ + create_user("testuser", "123", "testuser@gmail.com") + url = reverse('signin') + data = { + "username":"testuser", + "password":"123" + } + + response = api_client.post(url, data=data, format="json") + assert response.status_code == status.HTTP_200_OK + From 939700506bea81f2f52ea9bedaaa0619865833d2 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 5 Mar 2024 15:46:36 +0300 Subject: [PATCH 099/128] Test create group api --- tests/test_groups_api.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/test_groups_api.py diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py new file mode 100644 index 0000000..0c1da58 --- /dev/null +++ b/tests/test_groups_api.py @@ -0,0 +1,25 @@ +"""Test cases for groups APIs""" +import pytest +from django.urls import reverse +from rest_framework import status + +@pytest.mark.django_db +def test_create_group_api(api_client, authenticate_user_with_token): + """ + Test create group api endpoint + :param client + :return None + """ + + token = authenticate_user_with_token() + + url = reverse("group") + data = { + "name":"test group" + } + api_client.credentials(HTTP_AUTHORIZATION='Token '+ token) + response = api_client.post(url, data=data, format="json") + + api_client.credentials() + + assert response.status_code == status.HTTP_201_CREATED From cb5c640b3bc43c37a5f077bca00a14880b38180a Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 5 Mar 2024 22:56:55 +0300 Subject: [PATCH 100/128] Fix pylint warnings and errors --- tests/conftest.py | 31 +++++++++++++++++++++++-------- tests/test_groups_api.py | 5 +++-- tests/test_users_api.py | 14 ++++++++------ 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1aad3f5..08b82a8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,31 +1,46 @@ +"""Reusable test functions""" import pytest -from django.contrib.auth.models import User from rest_framework.test import APIClient +from django.contrib.auth.models import User from django.urls import reverse -@pytest.fixture -def api_client(): + +@pytest.fixture(name="api_client") +def fixture_api_client(): + """ + APIClient fixture + """ return APIClient() -@pytest.fixture -def create_user(db): + +@pytest.fixture(name="create_user") +def fixture_create_user(db): """ Create a user object :param db :return None """ def make_user(username, password, email): - User.objects.create_user(username=username, password=password, email=email) + User.objects.create_user(username=username, + password=password, email=email) return make_user + @pytest.fixture def authenticate_user_with_token(db, api_client, create_user): + """ + Authenticate user with token after signin + :params db, api_client, create_user + """ def authenticate_user(): + """ + Authenticate user + """ create_user("testuser", "123", "testuser@gmail.com") url = reverse('signin') data = { - "username":"testuser", - "password":"123" + "username": "testuser", + "password": "123" } response = api_client.post(url, data=data, format="json") diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index 0c1da58..f3137c0 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -3,6 +3,7 @@ from django.urls import reverse from rest_framework import status + @pytest.mark.django_db def test_create_group_api(api_client, authenticate_user_with_token): """ @@ -15,9 +16,9 @@ def test_create_group_api(api_client, authenticate_user_with_token): url = reverse("group") data = { - "name":"test group" + "name": "test group" } - api_client.credentials(HTTP_AUTHORIZATION='Token '+ token) + api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) response = api_client.post(url, data=data, format="json") api_client.credentials() diff --git a/tests/test_users_api.py b/tests/test_users_api.py index 3d4d50e..4c4367b 100644 --- a/tests/test_users_api.py +++ b/tests/test_users_api.py @@ -1,8 +1,10 @@ +"""Test cases for users APIs""" import pytest from django.urls import reverse from django.contrib.auth.models import User from rest_framework import status + @pytest.mark.django_db def test_user_signup_api(api_client): """ @@ -13,9 +15,9 @@ def test_user_signup_api(api_client): url = reverse('signup') data = { - "username":"testuser", - "password":"123", - "email":"testuser@gmail.com" + "username": "testuser", + "password": "123", + "email": "testuser@gmail.com" } response = api_client.post(url, data=data, format="json") assert response.status_code == status.HTTP_201_CREATED @@ -23,6 +25,7 @@ def test_user_signup_api(api_client): user = User.objects.get(username="testuser") assert user.username == "testuser" + @pytest.mark.django_db def test_user_signin_api(api_client, create_user): """ @@ -33,10 +36,9 @@ def test_user_signin_api(api_client, create_user): create_user("testuser", "123", "testuser@gmail.com") url = reverse('signin') data = { - "username":"testuser", - "password":"123" + "username": "testuser", + "password": "123" } response = api_client.post(url, data=data, format="json") assert response.status_code == status.HTTP_200_OK - From b81b955b836fad1e6272086b2c30156e35ce0b9c Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 6 Mar 2024 00:05:37 +0300 Subject: [PATCH 101/128] Test GET method for retrieving list of groups --- tests/test_groups_api.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index f3137c0..27bf4d0 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -24,3 +24,17 @@ def test_create_group_api(api_client, authenticate_user_with_token): api_client.credentials() assert response.status_code == status.HTTP_201_CREATED + + +@pytest.mark.django_db +def test_get_groups_api(api_client, authenticate_user_with_token): + """Test GET method for retrieving groups""" + token = authenticate_user_with_token() + + url = reverse("group") + api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) + response = api_client.get(url) + + api_client.credentials() + + assert response.status_code == status.HTTP_200_OK From 29a7e48eed2bc736afe51a4cdd159d3f71d98ac5 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 6 Mar 2024 00:08:09 +0300 Subject: [PATCH 102/128] Add default ordering to fix pytest pagination warning --- group/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/group/views.py b/group/views.py index 0d7eadd..6899d08 100644 --- a/group/views.py +++ b/group/views.py @@ -35,7 +35,7 @@ def get_object(self, request=None, group_id=None): def get(self, request, *args, **kwargs): """Retrieve a list of all the groups""" - queryset = self.get_queryset() + queryset = self.get_queryset().order_by('id') page = self.paginate_queryset(queryset) if page is not None: serializer = self.serializer_class(page, many=True) From 5f4d32949db78a6856597016473b7a80edc86f9a Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 7 Mar 2024 12:51:24 +0300 Subject: [PATCH 103/128] Create test user and group --- tests/conftest.py | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 08b82a8..04ec501 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ from rest_framework.test import APIClient from django.contrib.auth.models import User from django.urls import reverse +from group.models import Group @pytest.fixture(name="api_client") @@ -21,8 +22,8 @@ def fixture_create_user(db): :return None """ def make_user(username, password, email): - User.objects.create_user(username=username, - password=password, email=email) + return User.objects.create_user(username=username, + password=password, email=email) return make_user @@ -32,17 +33,43 @@ def authenticate_user_with_token(db, api_client, create_user): Authenticate user with token after signin :params db, api_client, create_user """ - def authenticate_user(): + def authenticate_user(username, password, email): """ Authenticate user """ - create_user("testuser", "123", "testuser@gmail.com") + if not User.objects.filter(username=username).exists(): + create_user(username, password, email) url = reverse('signin') data = { - "username": "testuser", - "password": "123" + "username": username, + "password": password } response = api_client.post(url, data=data, format="json") + print(response.data) return response.data["token"] return authenticate_user + + +@pytest.fixture(name="group_creator") +def fixture_group_creator(db, create_user): + """Create a user who is a group creator to test group delete""" + + return create_user(username='test_creator', + password='123', email='testcreator@gmail.com') + + +@pytest.fixture(name="another_user") +def fixture_another_user(db, create_user): + """Create a user object who isn't a group creator to test group delete""" + + return create_user(username='not_creator', + password='123', email='notcreator@gmail.com') + + +@pytest.fixture +def test_group(db, group_creator): + """Create a test group""" + group = Group.objects.create(name="Test group", creator=group_creator) + group.members.set([group_creator]) + return group From 549eedf234d9402d56138a452263ccf16420024e Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 7 Mar 2024 12:52:14 +0300 Subject: [PATCH 104/128] Test successful group deletion --- tests/test_groups_api.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index 27bf4d0..cedd1a3 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -12,7 +12,8 @@ def test_create_group_api(api_client, authenticate_user_with_token): :return None """ - token = authenticate_user_with_token() + token = authenticate_user_with_token("testuser", "123", + "testuser@gmail.com") url = reverse("group") data = { @@ -29,7 +30,8 @@ def test_create_group_api(api_client, authenticate_user_with_token): @pytest.mark.django_db def test_get_groups_api(api_client, authenticate_user_with_token): """Test GET method for retrieving groups""" - token = authenticate_user_with_token() + token = authenticate_user_with_token("testuser", "123", + "testuser@gmail.com") url = reverse("group") api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) @@ -38,3 +40,19 @@ def test_get_groups_api(api_client, authenticate_user_with_token): api_client.credentials() assert response.status_code == status.HTTP_200_OK + + +@pytest.mark.django_db +def test_group_delete_success(api_client, authenticate_user_with_token, + test_group): + """Test successful group deletion""" + token = authenticate_user_with_token("test_creator", "123", + "testcreator@gmail.com") + + url = reverse("group") + api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) + response = api_client.delete(f"{url}{test_group.id}/") + + api_client.credentials() + + assert response.status_code == status.HTTP_204_NO_CONTENT From 79f83632fda4e2cbe58761f9b447aaa9bdc29188 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 7 Mar 2024 13:17:08 +0300 Subject: [PATCH 105/128] Test for permission denied when deleting group --- tests/test_groups_api.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index cedd1a3..99c102b 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -56,3 +56,20 @@ def test_group_delete_success(api_client, authenticate_user_with_token, api_client.credentials() assert response.status_code == status.HTTP_204_NO_CONTENT + + +@pytest.mark.django_db +def test_group_delete_permission_denied(api_client, + authenticate_user_with_token, + test_group): + """Test for permission denied if user is not a group creator""" + token = authenticate_user_with_token("not_creator", "123", + "notcreator@gmail.com") + + url = reverse("group") + api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) + response = api_client.delete(f"{url}{test_group.id}/") + + api_client.credentials() + + assert response.status_code == status.HTTP_403_FORBIDDEN From 922736531d4d74f673b1bc29430e7fc3b48984e0 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 7 Mar 2024 14:58:58 +0300 Subject: [PATCH 106/128] Test deletion if group is not existent --- tests/test_groups_api.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index 99c102b..e49b661 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -73,3 +73,20 @@ def test_group_delete_permission_denied(api_client, api_client.credentials() assert response.status_code == status.HTTP_403_FORBIDDEN + + +@pytest.mark.django_db +def test_group_delete_group_not_found(api_client, + authenticate_user_with_token): + """Test deletion if group is not existent""" + token = authenticate_user_with_token("testuser", "123", + "testuser@gmail.com") + + url = reverse("group") + non_existent_group_id = 0 + api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) + response = api_client.delete(f"{url}{non_existent_group_id}/") + + api_client.credentials() + + assert response.status_code == status.HTTP_404_NOT_FOUND From 45b6b37616269345c78bfc544a94e1657522acb7 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 7 Mar 2024 15:53:44 +0300 Subject: [PATCH 107/128] Rename test function name --- tests/test_groups_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index e49b661..1dc316a 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -5,7 +5,7 @@ @pytest.mark.django_db -def test_create_group_api(api_client, authenticate_user_with_token): +def test_group_create(api_client, authenticate_user_with_token): """ Test create group api endpoint :param client @@ -28,7 +28,7 @@ def test_create_group_api(api_client, authenticate_user_with_token): @pytest.mark.django_db -def test_get_groups_api(api_client, authenticate_user_with_token): +def test_group_list(api_client, authenticate_user_with_token): """Test GET method for retrieving groups""" token = authenticate_user_with_token("testuser", "123", "testuser@gmail.com") From f13416a1235d11736b4b9023eedf7163d8c264a3 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 7 Mar 2024 17:41:24 +0300 Subject: [PATCH 108/128] Test length of the list returned --- tests/test_groups_api.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index 1dc316a..e7217af 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -2,6 +2,7 @@ import pytest from django.urls import reverse from rest_framework import status +from group.models import Group @pytest.mark.django_db @@ -28,11 +29,14 @@ def test_group_create(api_client, authenticate_user_with_token): @pytest.mark.django_db -def test_group_list(api_client, authenticate_user_with_token): +def test_group_list(api_client, authenticate_user_with_token, group_creator): """Test GET method for retrieving groups""" token = authenticate_user_with_token("testuser", "123", "testuser@gmail.com") + Group.objects.create(name="group 1", creator=group_creator) + Group.objects.create(name="group 2", creator=group_creator) + url = reverse("group") api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) response = api_client.get(url) @@ -40,6 +44,8 @@ def test_group_list(api_client, authenticate_user_with_token): api_client.credentials() assert response.status_code == status.HTTP_200_OK + expected_size = Group.objects.count() + assert response.json().get('count') == expected_size @pytest.mark.django_db From 4d67e6354d97207793047c37ac5c020aa5bdc3ae Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 11 Mar 2024 22:47:38 +0300 Subject: [PATCH 109/128] Add coverage package for checking code coverage --- .coverage | Bin 0 -> 53248 bytes requirements.txt | 1 + 2 files changed, 1 insertion(+) create mode 100644 .coverage diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..cc83811c4fe557631b2e99b0dbd1f2a2eaa2b0e0 GIT binary patch literal 53248 zcmeI4TZ|k>6+pYYW_upn-L>zT+1W=cA-i6@-iIB_*jDTqv#}!E1ZN#Sh@5fnOz&>v zndxzNj~_7sHn9|ufIvJxA|T>}2oVH=4-iryAVK&*VB;?*2q{8<&0VIF~ z{=W&V9n+H8d|rO?iNIQ_*nwx2Y%hG&4?etb;P`@h{J=+#ESO=Lxg}$=W7{_Kfa$qs z&ARQG%TC2Mom$x`S%FhqF@sfGST}sT3^zJf#%^_BIK%zntmu@PS75KOD(aq7wY>A@ zDf|31H;7sb>~ld_z$&qwl^R%>TO#)<)3cXt&#skhKa66FQ=ZC9J@vUvGTYxTKW+0q zSzd|#b+`3~6|}nY;4-VID^ToCrwbE~s~YgU{Z?`F++ z0>`bH_Bp%M2<-B0J2(x{#KGXjd&BBF2^~5adPZ%<=(L<~;~AMp&dV&gnk%rj9HD9E z%ww)Ks!I&{ryJEe3=Z=M8xGu!JU8+U+^Dnfd2DVomaYD_4W~d3BN}=&YR>6~-5mUB zv*kI>yHRteP3Q#6fP1>t?CyS9Vp(aQ^G{dU^R1%Q2wZ+$WP@LvXAj!N-c&L>J}y6f znkNe2DK0sI-#kgTB~7zC z>sGL8u8$pxS>TghYIg77Mu9^J#d&yCpL$f`AwQkx^s8*qhkw!*GL*DJK5U=gcE+l( z)ue7Y9-Fdk4G!-BCK#42W^66FjezGbT=MjUdrvFgu!7dLV9%_kxH+5NpKyKOVYgMd zZTWjdsX4i7`LGK4ljf4^R&1*l-W!<5gFb81@l77X`n%HpRvrF%sAJ{KB#%klTws|$ z&L*9hU+kn>wQA+)eg(JTuvWJA!{mf^!bOBRnv-l&`fQ4J)F~>Bw6J1{`e*8l3DjD( z%Hec}L5bCPcGX^2U3fS8m=)N8Q?*Sn;lc34I&1l6$+MY~-Akt?$(_;wMyogvkFZNl z!Be@laXFdYx>a6lM3XUGXNo?%saGu#c66z}l`jvm1(!YBiRTHhi{XIFumxD$ z!Vz2wDG00RxlU#ZHuy4qM`C|?AOR$R1dsp{Kmter2_OL^fCP{L50|T(x`i6Xe~j0RUmD*vzF?d(jvDtGdHsg|C;j{S=k-N> zR?liTwZCh>)n3%T%$)H+0!RP}AOR$R1dsp{Kmter39KV9K1QU4*}CfoPB6RTxsCd) zZ+nhaaUN&i^=4n+&W{plce}wE$36?H+z63&wW}Il1vFy?A{`VO_A4)5N2R&pm?zhZ zIkyGn2%E5zRcFOx zzemGUNQSEhhht~xbnPwQw;D2BU`4?ox?e@v)wbQGM^$>C;s=k_bI$xf3KO`B*}y5atRXbX8{W8>IHZE%7dYZ~(XY^%qOa4d^b-9YeVP7@{*b;vzs)}5|3!L{eukc+CHg2mMEB6`^uyGo zL)4&M##_c4#x>(IbH)P+AOR$R1dsp{Kmter2_OL^fCP}h|AIhjEFq`+#`aw(4vw<3 z>Cu~ieE03Ehi)ZUjc110+0gLKzx-{CCi9@qUy{`v zJ27&5Qduy|D$<$M5S$HtWalk)5OjlzIskG&>F#GT*T0XvzWnceUmv@e=-WqL`bPEY zb0_jIZNJc^_JU!rqV|C7Q7C27q@R62z1*|^@*jR}s0JtuMb$y-GpYtsQTbT literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt index 8222ed0..4dde776 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ psycopg2==2.9.9 pylint==3.0.2 pylint-django==2.5.5 pytest-django==4.8.0 +coverage==7.4.3 drf-yasg==1.21.7 \ No newline at end of file From eb9144c492561456f3357d49c6ab1953c623dc3d Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 12 Mar 2024 00:17:39 +0300 Subject: [PATCH 110/128] Test successful members addition to group --- .coverage | Bin 53248 -> 53248 bytes tests/test_groups_api.py | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/.coverage b/.coverage index cc83811c4fe557631b2e99b0dbd1f2a2eaa2b0e0..48fda779983a3a6dda15cd1da938be696f9e4e47 100644 GIT binary patch delta 108 zcmZozz}&Eac>`MmI5EuRdh z91A0-h@4&ZeLGQEAX7*7-`PLEuDky)`CmEtL0`JKs1Qh0sE(~n`}R8H-9}}p-%dUF L9JV>Sf2jii?nNcG delta 104 zcmZozz}&Eac>`Mm;M1& diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index e7217af..2a40b1e 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -1,6 +1,7 @@ """Test cases for groups APIs""" import pytest from django.urls import reverse +from django.contrib.auth.models import User from rest_framework import status from group.models import Group @@ -96,3 +97,29 @@ def test_group_delete_group_not_found(api_client, api_client.credentials() assert response.status_code == status.HTTP_404_NOT_FOUND + + +@pytest.mark.django_db +def test_group_add_members_successful(api_client, + authenticate_user_with_token, + group_creator): + """ + Test successful addition of new members to a group by the group creator + """ + token = authenticate_user_with_token("test_creator", "123", + "testcreator@gmail.com") + group = Group.objects.create(name="group 1", creator=group_creator) + user_1 = User.objects.create_user("user_1", "123", "user_1@gmail.com") + user_2 = User.objects.create_user("user_2", "123", "user_2@gmail.com") + + url = reverse("group") + data = { + "members": [user_1.id, user_2.id], + "group_id": group.id + } + api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) + response = api_client.patch(url, data=data, format="json") + + api_client.credentials() + + assert response.status_code == status.HTTP_200_OK From 5316c4d981cfcc345749980fa6edd48129935d0a Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 12 Mar 2024 00:56:40 +0300 Subject: [PATCH 111/128] Test for permission denied when adding new members to group --- .coverage | Bin 53248 -> 53248 bytes tests/test_groups_api.py | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/.coverage b/.coverage index 48fda779983a3a6dda15cd1da938be696f9e4e47..6456f2778fdd4b66e4042df542afdf06a46b74c2 100644 GIT binary patch delta 566 zcmY+8J!lhQ9EbDXo6EIw@B3apF838}Y^n z)lxM*Qz=fTYo+3~%3)_#i$3JKp<-XQtFJhv^vv{>C;KCCgeOoK&hZoq!7ZLdL0IMk zLPCLEo@nxkH8f($>ppgwK4Ifjpr47u2I)P<$O_q@ujO@u_qZ;GaYa|oWFVJBIR9byA4 z#uO&eHr=M1Zs-EN(QElpZ^Rm{e;rj#7gJ{Y{qJ8N$2--|th3XxkzX?0TQR<2N%ufs zc(X@yA*!`wTl<(4_0H%A6i{8D>PSz--B3jE)q27ng^wNW1VWw*34x%uK|xpC9agRf sYRb@;)$FU`T5IXOU3{7O18W3T={0k;%;(-_wk%KYR*`_r`;$^-!^WOK(`(9xR6sEw8 ztqbUIm^ivZ+HRx>tHLYcj!+Pe3WgxTb@&-Rg41vU4$r!rZIBD<!KeA&b%dL^p%1N)o1`Du9VqRHNC{h*1{#WOBij zY!YC4$j8HSZTUIuBlpaRO$1CInK2cjCF{5*F9FG><^~VdRz~Gg*<9AJ&H-y`cHneo zkbd3hCT+>acEz>HcBz3XBxjW&+Ekj6ojsJCW4m;iX7B*Ijk|?u^bW-Y8MmTq7z@XQ zt7rkHkqYm^N&FT+5Ta-ZC54~DYkVD#qgD7A7U36~To{rkCqtdO3Od+|*NwSp3W)zh zm3I#W!G4z1n|{0B(vW?osH!rE{Z&`zr;tp)WS1L34{PNI%QK`1=x1qiEp}fE=(A8M zL%Y}vjWT5Cgs4#%(bix5{`IE2m@AGkUyHr0 z%@Lr_&cHWN Date: Tue, 12 Mar 2024 01:37:59 +0300 Subject: [PATCH 112/128] Test for non existent user(s) when adding new members to group --- .coverage | Bin 53248 -> 53248 bytes tests/test_groups_api.py | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/.coverage b/.coverage index 6456f2778fdd4b66e4042df542afdf06a46b74c2..695b1b31fe640ffff8383f9a9fdcd6acdaf8ad2b 100644 GIT binary patch delta 91 zcmV-h0HptbpaX!Q1F!~w0oaoVfFKu54@D0>4>J!b4RRp|8tZ1j#wTX5Cj1UIuQO0>{hm2XLe=m%IuZF_f`hqTN%8!0<&w6l|VzRA;SOw delta 87 zcmV-d0I2_fpaX!Q1F!~w0o;=ZfFKu94@nO}4>u1j4 Date: Tue, 12 Mar 2024 01:47:57 +0300 Subject: [PATCH 113/128] Test addition of new members if group is not existent --- .coverage | Bin 53248 -> 53248 bytes tests/test_groups_api.py | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/.coverage b/.coverage index 695b1b31fe640ffff8383f9a9fdcd6acdaf8ad2b..7cc3def04fc6b3b63aa03b89658fe332b8ef5916 100644 GIT binary patch delta 90 zcmV-g0HyzcpaX!Q1F!~w0o9WSfFKu24?+(*4=@iV4;~K{4+#$d4)+e=4%M?kfU^z) w^XrrLj#nWc5Cj1UKoEWgb}QSiGrKZ&W%kP8dn<$Qtqk5<8C-7#vulr)Kp8$DIRF3v delta 91 zcmV-h0HptbpaX!Q1F!~w0oaoVfFKu54@D0>4>J!b4{hm2XLe=m%IuZF_f`hqTN%8!0<&w6l|VgAA)f#M diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index 41a88e6..425a848 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -174,3 +174,29 @@ def test_group_add_members_bad_request(api_client, api_client.credentials() assert response.status_code == status.HTTP_400_BAD_REQUEST + + +@pytest.mark.django_db +def test_group_add_members_group_not_found(api_client, + authenticate_user_with_token, + group_creator): + """ + Test group not found when adding new members to a group + """ + token = authenticate_user_with_token("test_creator", "123", + "testcreator@gmail.com") + user_1 = User.objects.create_user("user_1", "123", "user_1@gmail.com") + + non_existent_group_id = 0 + + url = reverse("group") + data = { + "members": [user_1.id], + "group_id": non_existent_group_id + } + api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) + response = api_client.patch(url, data=data, format="json") + + api_client.credentials() + + assert response.status_code == status.HTTP_404_NOT_FOUND From 9e8e5cf339b6497c1e6ebccf8813592cc73997f5 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 12 Mar 2024 02:52:52 +0300 Subject: [PATCH 114/128] Test for retrieval of a single group --- .coverage | Bin 53248 -> 53248 bytes group/urls.py | 2 +- tests/test_groups_api.py | 19 +++++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.coverage b/.coverage index 7cc3def04fc6b3b63aa03b89658fe332b8ef5916..8d791c4648f20747d2e1f007334039c85cfe90ca 100644 GIT binary patch delta 74 zcmV-Q0JZ;spaX!Q1F!~w0n?KQfFKu04?qt%4=xWR4;&8@4+jqZ4)qS+4%4#{5S0#- g)s7$mJ(Kp1R0bgs1OW*|lTeQu17)l$vulr)KtjA1I{*Lx delta 72 zcmV-O0Jr~upaX!Q1F!~w0o9WSfFKu24?+(*4=@iV4;~K{4+#$d4)+e=4%M>}5SI>< e(~ckk0h9EOR0baq1OW*^lTMEs0R^*bkCi~_)D+hM diff --git a/group/urls.py b/group/urls.py index 083c474..8d610e0 100644 --- a/group/urls.py +++ b/group/urls.py @@ -9,7 +9,7 @@ urlpatterns = [ path('', GroupApiView.as_view(), name='group'), path('/', GroupApiView.as_view()), - path('detail//', GroupDetailApiView.as_view()), + path('detail//', GroupDetailApiView.as_view(), name='group'), path('users//', GroupDetailApiView.as_view()), path('messages', MessageAPIView.as_view()), path('/messages//', diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index 425a848..cfcd42e 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -200,3 +200,22 @@ def test_group_add_members_group_not_found(api_client, api_client.credentials() assert response.status_code == status.HTTP_404_NOT_FOUND + + +@pytest.mark.django_db +def test_group_retrieve_single_group(api_client, + authenticate_user_with_token, + group_creator): + """Test GET method for retrieving a single group""" + token = authenticate_user_with_token("test_creator", "123", + "testcreator@gmail.com") + + group = Group.objects.create(name="group 1", creator=group_creator) + + url = reverse("group", kwargs={"group_id": group.id}) + api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) + response = api_client.get(url) + + api_client.credentials() + + assert response.status_code == status.HTTP_200_OK From 647da0ea76b4607aacbfd1e3a8709eecc416991e Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 12 Mar 2024 03:34:31 +0300 Subject: [PATCH 115/128] Test successful removal of a user(s) from group --- group/urls.py | 2 +- tests/test_groups_api.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/group/urls.py b/group/urls.py index 8d610e0..807b02d 100644 --- a/group/urls.py +++ b/group/urls.py @@ -10,7 +10,7 @@ path('', GroupApiView.as_view(), name='group'), path('/', GroupApiView.as_view()), path('detail//', GroupDetailApiView.as_view(), name='group'), - path('users//', GroupDetailApiView.as_view()), + path('users//', GroupDetailApiView.as_view(), name='group'), path('messages', MessageAPIView.as_view()), path('/messages//', MessageDetailAPIView.as_view()), diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index cfcd42e..52880a2 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -219,3 +219,27 @@ def test_group_retrieve_single_group(api_client, api_client.credentials() assert response.status_code == status.HTTP_200_OK + + +@pytest.mark.django_db +def test_group_successful_delete_user(api_client, + authenticate_user_with_token, + group_creator): + """Test successful removal of a user(s) from group""" + token = authenticate_user_with_token("test_creator", "123", + "testcreator@gmail.com") + + group = Group.objects.create(name="group 1", creator=group_creator) + user = User.objects.create_user("user_to_delete", "123", + "usertodelete@gmail.com") + group.members.set([user]) + + url = reverse("group", kwargs={"user_id": user.id}) + data = {"group_id": group.id} + + api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) + response = api_client.delete(url, data=data, format="json") + + api_client.credentials() + + assert response.status_code == status.HTTP_204_NO_CONTENT From 7205d209799470fb900316a26de5b5d875cd4374 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 12 Mar 2024 03:50:41 +0300 Subject: [PATCH 116/128] Test if only group creator can remove user(s) from group --- .coverage | Bin 53248 -> 53248 bytes tests/test_groups_api.py | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/.coverage b/.coverage index 8d791c4648f20747d2e1f007334039c85cfe90ca..795053b63eeb1e673fa1fdf306c265529f6b6bcd 100644 GIT binary patch delta 108 zcmZozz}&Eac>`Mm6xD=3SAocI;pI_JA|Cjv#|M~TQAKNeTpC^Cq3uaRn0LlhT_Uo5rOU~C$ M&X?Yt-9Oa<07O+K?EnA( delta 92 zcmV-i0HgnapaX!Q1F!~w0n?KQfFKu04?qt%4=xWR4;&8@4+jqZ4)qS+4%4#{5S0#- y%Z>{d6&eHq2`n1^&HVb;NB?yH|MTnrOg#jX?~YRjArJ%s2}F}fj~%mXkCi}|n<3@^ diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index 52880a2..38a29c3 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -243,3 +243,27 @@ def test_group_successful_delete_user(api_client, api_client.credentials() assert response.status_code == status.HTTP_204_NO_CONTENT + + +@pytest.mark.django_db +def test_group_only_creator_delete_user(api_client, + authenticate_user_with_token, + group_creator): + """Test that only group creator can remove a user(s) from group""" + token = authenticate_user_with_token("testuser", "123", + "testuser@gmail.com") + + group = Group.objects.create(name="group 1", creator=group_creator) + user = User.objects.create_user("user_to_delete", "123", + "usertodelete@gmail.com") + group.members.set([user]) + + url = reverse("group", kwargs={"user_id": user.id}) + data = {"group_id": group.id} + + api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) + response = api_client.delete(url, data=data, format="json") + error = response.json().get("error") + assert error == "Only group creator can remove members" + + api_client.credentials() From ed752c2195753170e4804b6fec31032a9887cd64 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Tue, 12 Mar 2024 04:00:30 +0300 Subject: [PATCH 117/128] Test if group creator cannot be removed from group --- .coverage | Bin 53248 -> 53248 bytes tests/test_groups_api.py | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/.coverage b/.coverage index 795053b63eeb1e673fa1fdf306c265529f6b6bcd..fb898caea51d4c7ba5f65cc2a9897087f9125675 100644 GIT binary patch delta 71 zcmV-N0J#5vpaX!Q1F!~w0mzdFfFKt?4>u1l4HR0b*#1OW+FlSq#u1TSOT7qe@Rl|Us57k&T$ delta 72 zcmV-O0Jr~upaX!Q1F!~w0n3vIfFKt_4>}Jr4=4{F4;Bv%4*?GN4(|@u4$HF<5RDF# e$c`cbACv2jR0byy1OW+9lSPjr0UNVxkCj0GR~454 diff --git a/tests/test_groups_api.py b/tests/test_groups_api.py index 38a29c3..6c14f5e 100644 --- a/tests/test_groups_api.py +++ b/tests/test_groups_api.py @@ -267,3 +267,26 @@ def test_group_only_creator_delete_user(api_client, assert error == "Only group creator can remove members" api_client.credentials() + + +@pytest.mark.django_db +def test_group_cannot_remove_creator_from_group(api_client, + authenticate_user_with_token, + group_creator): + """Test that group creator cannot be removed from group""" + token = authenticate_user_with_token("test_creator", "123", + "testcreator@gmail.com") + + group = Group.objects.create(name="group 1", creator=group_creator) + group.members.set([group_creator]) + + url = reverse("group", kwargs={"user_id": group_creator.id}) + data = {"group_id": group.id} + + api_client.credentials(HTTP_AUTHORIZATION='Token ' + token) + response = api_client.delete(url, data=data, format="json") + error = response.json().get("error") + assert error == "Cannot remove creator from the group" + assert response.status_code == status.HTTP_403_FORBIDDEN + + api_client.credentials() From ebc80be34bc47e48380d0e9c3a50a4861be66ef8 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 13 Mar 2024 03:44:08 +0300 Subject: [PATCH 118/128] Add github workflow for linting and testing --- .github/workflows/run_test.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/run_test.yml diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml new file mode 100644 index 0000000..1a44ebf --- /dev/null +++ b/.github/workflows/run_test.yml @@ -0,0 +1,33 @@ +name: Lint, Test, and Generate Coverage report + +on: +pull_request: + branches: + - develop + - main + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + version: ["3.9"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Lint with pylint and flake8 + run: | + pylint user/*.py, group/*.py + flake8 user/*.py, group/*.py + - name: Test with pytest + run: coverage run -m pytest -v -s + - name: Generate coverage report + run: coverage report -m From 4a2a28fd9d4eaced45c45ece31934e7777d75750 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 13 Mar 2024 04:11:19 +0300 Subject: [PATCH 119/128] Fix indentation error --- .github/workflows/run_test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml index 1a44ebf..aed8e3c 100644 --- a/.github/workflows/run_test.yml +++ b/.github/workflows/run_test.yml @@ -1,10 +1,10 @@ name: Lint, Test, and Generate Coverage report on: -pull_request: - branches: - - develop - - main + pull_request: + branches: + - develop + - main jobs: build: From 7aeb53d7fd2f48f945e349456f1d7ba02e8eb1a7 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Wed, 13 Mar 2024 14:12:17 +0300 Subject: [PATCH 120/128] Set working directory --- .github/workflows/run_test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml index aed8e3c..5bdd6ca 100644 --- a/.github/workflows/run_test.yml +++ b/.github/workflows/run_test.yml @@ -24,6 +24,7 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt - name: Lint with pylint and flake8 + working-directory: post-it-backend run: | pylint user/*.py, group/*.py flake8 user/*.py, group/*.py From 6628d6eaaf10c6adf62df60bdfcb33ce7b1e3a1b Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 14 Mar 2024 00:27:27 +0300 Subject: [PATCH 121/128] Add flake8 configuration file --- .flake8 | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..6296348 --- /dev/null +++ b/.flake8 @@ -0,0 +1,15 @@ +[flake8] +extend-ignore = E203 +include = + user/*.py, + group/*.py +exclude = + .git, + __pycache__, + docs/source/conf.py, + old, + build, + dist, + user/migrations/*, + group/migrations/* +max-complexity = 10 \ No newline at end of file From 9206dc75339e3f797eb499ad492f93028b224bbc Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 14 Mar 2024 11:32:11 +0300 Subject: [PATCH 122/128] Ignore migrations files when running pylint --- .github/workflows/run_test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml index 5bdd6ca..a2e5a02 100644 --- a/.github/workflows/run_test.yml +++ b/.github/workflows/run_test.yml @@ -26,8 +26,9 @@ jobs: - name: Lint with pylint and flake8 working-directory: post-it-backend run: | - pylint user/*.py, group/*.py - flake8 user/*.py, group/*.py + pylint --ignore=migrations group + pylint --ignore=migrations user + flake8 user, group - name: Test with pytest run: coverage run -m pytest -v -s - name: Generate coverage report From e7c799dec39a564a92ab58f6a7be46fbd97cc898 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 14 Mar 2024 11:34:09 +0300 Subject: [PATCH 123/128] Remove working-directory --- .github/workflows/run_test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml index a2e5a02..febb4e7 100644 --- a/.github/workflows/run_test.yml +++ b/.github/workflows/run_test.yml @@ -24,7 +24,6 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt - name: Lint with pylint and flake8 - working-directory: post-it-backend run: | pylint --ignore=migrations group pylint --ignore=migrations user From d6c316e71a535eb648aa701133f73c97ad47ee8a Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 14 Mar 2024 11:40:30 +0300 Subject: [PATCH 124/128] Run flake8 on user in a separate command --- .github/workflows/run_test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml index febb4e7..e5b4a8f 100644 --- a/.github/workflows/run_test.yml +++ b/.github/workflows/run_test.yml @@ -27,7 +27,8 @@ jobs: run: | pylint --ignore=migrations group pylint --ignore=migrations user - flake8 user, group + flake8 group + flake8 user - name: Test with pytest run: coverage run -m pytest -v -s - name: Generate coverage report From 51b416c0bd327826a873bfc9f24aa1a68cdbd94e Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 14 Mar 2024 14:03:43 +0300 Subject: [PATCH 125/128] Add dummy secret key --- .github/workflows/run_test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml index e5b4a8f..6d9e86e 100644 --- a/.github/workflows/run_test.yml +++ b/.github/workflows/run_test.yml @@ -30,6 +30,8 @@ jobs: flake8 group flake8 user - name: Test with pytest + env: + SECRET_KEY: ${{ secrets.SECRET_KEY }} run: coverage run -m pytest -v -s - name: Generate coverage report run: coverage report -m From ce31fc05a72230459d1599e359e14da61912941d Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Thu, 14 Mar 2024 14:21:29 +0300 Subject: [PATCH 126/128] Add other environment variables --- .github/workflows/run_test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml index 6d9e86e..2f01363 100644 --- a/.github/workflows/run_test.yml +++ b/.github/workflows/run_test.yml @@ -32,6 +32,11 @@ jobs: - name: Test with pytest env: SECRET_KEY: ${{ secrets.SECRET_KEY }} + DB_NAME: ${{ secrets.DB_NAME }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_HOST: ${{ secrets.DB_HOST }} + DB_PORT: ${{ secrets.DB_PORT }} run: coverage run -m pytest -v -s - name: Generate coverage report run: coverage report -m From 75f6c942d33ec732cb1331e5c878320117eaef4b Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 18 Mar 2024 11:50:10 +0300 Subject: [PATCH 127/128] Add postgres service to workflow --- .github/workflows/run_test.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml index 2f01363..ea933e2 100644 --- a/.github/workflows/run_test.yml +++ b/.github/workflows/run_test.yml @@ -12,6 +12,18 @@ jobs: strategy: matrix: version: ["3.9"] + services: + postgres: + image: postgres + env: + POSTGRES_PASSWORD: admin + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 steps: - uses: actions/checkout@v3 From 315c7c1f1ab8e923083e6f002dd40570c37aa7c4 Mon Sep 17 00:00:00 2001 From: APIYOJENNIFER Date: Mon, 18 Mar 2024 12:11:26 +0300 Subject: [PATCH 128/128] Move postgres password to github secrets --- .github/workflows/run_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml index ea933e2..fb942fd 100644 --- a/.github/workflows/run_test.yml +++ b/.github/workflows/run_test.yml @@ -16,7 +16,7 @@ jobs: postgres: image: postgres env: - POSTGRES_PASSWORD: admin + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} options: >- --health-cmd pg_isready --health-interval 10s