From ca1da650b8588eb6c89e79ef7bbecfd9fe3e03a9 Mon Sep 17 00:00:00 2001 From: Abdul-Muqadim-Arbisoft Date: Mon, 4 Sep 2023 11:34:56 +0500 Subject: [PATCH 1/4] celery tasks for welcome mail for signup and the task for the reminder mail to user to login back --- README.md | 6 +++ UniManage/__init__.py | 5 +++ UniManage/celery.py | 20 ++++++++++ UniManage/settings.py | 32 ++++++++++++++- celerybeat-schedule.db | Bin 0 -> 16384 bytes db.sqlite3 | Bin 208896 -> 204800 bytes project/migrations/0001_initial.py | 2 +- project/migrations/0002_initial.py | 2 +- user/admin.py | 32 ++++++++++++--- user/migrations/0001_initial.py | 13 +++++- user/models.py | 15 +++++++ user/tasks.py | 61 +++++++++++++++++++++++++++++ user/views.py | 12 +++++- 13 files changed, 188 insertions(+), 12 deletions(-) create mode 100644 UniManage/celery.py create mode 100644 celerybeat-schedule.db create mode 100644 user/tasks.py diff --git a/README.md b/README.md index f7a02f0..9c75ef0 100644 --- a/README.md +++ b/README.md @@ -402,3 +402,9 @@ pip install django==4.2.3 djangorestframework django-rest-framework-simplejwt dj ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. + +celery command: + /Users/abdul.muqadim/Library/Python/3.9/bin/celery -A UniManage worker --loglevel=info + +/Users/abdul.muqadim/Library/Python/3.9/bin/celery -A UniManage worker --loglevel=info +/Users/abdul.muqadim/Library/Python/3.9/bin/celery -A UniManage beat --loglevel=info diff --git a/UniManage/__init__.py b/UniManage/__init__.py index e69de29..ac5e271 100644 --- a/UniManage/__init__.py +++ b/UniManage/__init__.py @@ -0,0 +1,5 @@ +# __init__.py + +from .celery import app as celery_app + +__all__ = ('celery_app',) diff --git a/UniManage/celery.py b/UniManage/celery.py new file mode 100644 index 0000000..1722e94 --- /dev/null +++ b/UniManage/celery.py @@ -0,0 +1,20 @@ +# celery.py + +import os +from celery import Celery +from django.conf import settings + + +# set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'UniManage.settings') + +app = Celery('UniManage') + +# Using a string here means the worker doesn't have to serialize +# the configuration object to child processes. +app.config_from_object('django.conf:settings', namespace='CELERY') + +# Load task modules from all registered Django app configs. +app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) + +# celery.py (or wherever your Celery configurations are) diff --git a/UniManage/settings.py b/UniManage/settings.py index 8d4c80a..c33bd1c 100644 --- a/UniManage/settings.py +++ b/UniManage/settings.py @@ -13,6 +13,7 @@ from pathlib import Path import datetime import os +from datetime import timedelta # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -39,6 +40,7 @@ 'rest_framework', 'djoser', 'drf_yasg', + 'model_utils', ] REST_FRAMEWORK = { @@ -132,7 +134,7 @@ 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'user.middleware.CustomAuthenticationMiddleware', - ] +] AUTH_USER_MODEL = 'user.CustomUser' @@ -144,3 +146,31 @@ os.path.join(BASE_DIR, "project/static"), os.path.join(BASE_DIR, "user/static") ] + +# settings.py + +# Celery configurations +CELERY_BROKER_URL = 'redis://localhost:6379/0' +CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' +CELERY_ACCEPT_CONTENT = ['json'] +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = 'json' +CELERY_TIMEZONE = 'UTC' + +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + +# SMTP configuration +EMAIL_HOST = 'sandbox.smtp.mailtrap.io' +EMAIL_PORT = 587 # You can choose any of the provided ports, but 587 is commonly used for STARTTLS +EMAIL_USE_TLS = True +EMAIL_HOST_USER = '6bd0f1298b89cb' +EMAIL_HOST_PASSWORD = '75848b7312e651' + +CELERY_BEAT_SCHEDULE = { + 'check-login': { + 'task': 'user.tasks.check_last_login_and_send_email', + 'schedule': timedelta(minutes=1), # Run daily + }, +} + +CELERY_BEAT_MAX_LOOP_INTERVAL = 25 # Time in seconds. 600 seconds is 10 minutes. diff --git a/celerybeat-schedule.db b/celerybeat-schedule.db new file mode 100644 index 0000000000000000000000000000000000000000..db850730f84451d21cefc9c30c4d9b96a82fe65c GIT binary patch literal 16384 zcmeI%&2G~`5C`y0oTkaPjmt;)dV+$4FBLa#6$fOAg8|8KV70M_V#VDs-ZfP#Man6K z2jCTW4{n^fa0dy_JOCf-D1ig_idO%X)-&?1cV>TAi|$YhA=*N8J_@nzQ?V&T%ZFx) z-ZJs)ix z^u*8bd-yf{9DWKvgzv+{fgT(V&P@Ag6ao-{00bZa0SG_<0uX=z1paSDSC;&P4s=m+C(E@XR~(T1lknP}T>sYZblbzNXHU4Bjq0Lwg|TvD`2L_YP&{U+gF+V{m(ZC)1HxFqh+Sth)ZHowRpKicEXxHadkga>mD6W)&BtuAMm z_p)-8Nxty6v8KD6N~a}Xj75Am7AJfu;mdCxobWK=D>tWnb;{QqUr+dkjmYa1WM@IT z<_q76cB5Suky5Xyvc<$GH49!xwaXM4Jt`^p8&@5Q$Q}D{FdzT{2tWV=5P$##AOHaf lKmY;|IOhUCeqlfW0uX=z1Rwwb2tWV=5P$##AaLFVegp6zdq4mH literal 0 HcmV?d00001 diff --git a/db.sqlite3 b/db.sqlite3 index 8e627e40469fa9d5194a6c3add25eaa9e822818b..d7961c895c28cd04df232a810837dd24df8b64b8 100644 GIT binary patch delta 5275 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 + + +

Hello {username},

+

Thank you for registering with us. We're excited to have you on board!

+

Best Regards,

+

Your Team

+ + + """ + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [email], fail_silently=False, html_message=message) + except Exception as e: + # Here you can log the error or even retry the task if necessary. + self.retry(countdown=60 * 5, exc=e, max_retries=3) + + +@shared_task(bind=True) +def check_last_login_and_send_email(self): + # Get the duration and duration_type from the ReminderSetting + try: + reminder_setting = ReminderSetting.objects.first() + duration = reminder_setting.duration + duration_type = reminder_setting.duration_type + except AttributeError: + # Default values in case the setting isn't found + duration = 1 + duration_type = 'minutes' # Setting default to days for this test + + if duration_type == 'seconds': + time_threshold = datetime.now() - timedelta(seconds=duration) + elif duration_type == 'hours': + time_threshold = datetime.now() - timedelta(hours=duration) + elif duration_type == 'days': + time_threshold = datetime.now() - timedelta(days=duration) + else: # default to minutes + time_threshold = datetime.now() - timedelta(minutes=duration) + + # Get all users who joined more than the time_threshold ago + users_to_notify = CustomUser.objects.filter(last_login__lt=time_threshold) + + for user in users_to_notify: + subject = "We appreciate you!" + message = f"Hello {user.username},\n\n We have noticed that its been a while since you last visited. We would love to see you back on our platform!" + + + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email]) diff --git a/user/views.py b/user/views.py index 6642533..0204bdf 100644 --- a/user/views.py +++ b/user/views.py @@ -15,7 +15,7 @@ from django.contrib.auth import authenticate, login, logout from .serializers import (CustomUserSerializer, CustomUserRegistrationSerializer, ChangePasswordSerializer) from rest_framework.permissions import AllowAny - +from .tasks import send_welcome_email from utils.constants import ( SIGNUP_TEMPLATE, @@ -43,7 +43,12 @@ def get(request): @staticmethod def post(request): form = CustomUserCreationForm(request.POST) - return validate_and_save_form(form, request, 'login', SIGNUP_TEMPLATE, VALIDATION_ERROR_MSG) + response = validate_and_save_form(form, request, 'login', SIGNUP_TEMPLATE, VALIDATION_ERROR_MSG) + if form.is_valid(): + send_welcome_email.delay(email=form.cleaned_data.get('email'), + username=form.cleaned_data.get('username')) + + return response class LoginView(View): @@ -133,6 +138,8 @@ def post(self, request): serializer = CustomUserRegistrationSerializer(data=request.data) if serializer.is_valid(): serializer.save() + send_welcome_email.delay(email=serializer.validated_data.get('email'), + username=serializer.validated_data.get('username')) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -169,6 +176,7 @@ def post(self, request): return Response({"detail": "Invalid Credentials"}, status=status.HTTP_401_UNAUTHORIZED) + @api_view(['GET']) @permission_classes([IsAuthenticated]) def logout_api_view(request): From ea529fc9e05984dd6720e9350f676b5b3dc6d201 Mon Sep 17 00:00:00 2001 From: Abdul-Muqadim-Arbisoft Date: Mon, 4 Sep 2023 13:00:33 +0500 Subject: [PATCH 2/4] Country field added and its data to be fetched from the redis feature added --- UniManage/settings.py | 10 ++++++++++ celerybeat-schedule.db | Bin 16384 -> 0 bytes db.sqlite3 | Bin 204800 -> 204800 bytes project/migrations/0001_initial.py | 2 +- project/migrations/0002_initial.py | 4 ++-- project/tests.py | 19 +++++++++++-------- user/forms.py | 8 +++++++- user/migrations/0001_initial.py | 5 +++-- user/models.py | 2 +- user/serializers.py | 18 ++++++++++++++---- user/tasks.py | 2 -- user/tests.py | 25 +++++++++++++++++-------- user/urls.py | 12 +++++++----- user/views.py | 7 ++++++- utils/constants.py | 7 +++++-- utils/helpers.py | 16 +++++++++++++++- 16 files changed, 99 insertions(+), 38 deletions(-) delete mode 100644 celerybeat-schedule.db diff --git a/UniManage/settings.py b/UniManage/settings.py index c33bd1c..21eeef7 100644 --- a/UniManage/settings.py +++ b/UniManage/settings.py @@ -174,3 +174,13 @@ } CELERY_BEAT_MAX_LOOP_INTERVAL = 25 # Time in seconds. 600 seconds is 10 minutes. + +CACHES = { + 'default': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': 'redis://127.0.0.1:6379/1', + 'OPTIONS': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', + } + } +} diff --git a/celerybeat-schedule.db b/celerybeat-schedule.db deleted file mode 100644 index db850730f84451d21cefc9c30c4d9b96a82fe65c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI%&2G~`5C`y0oTkaPjmt;)dV+$4FBLa#6$fOAg8|8KV70M_V#VDs-ZfP#Man6K z2jCTW4{n^fa0dy_JOCf-D1ig_idO%X)-&?1cV>TAi|$YhA=*N8J_@nzQ?V&T%ZFx) z-ZJs)ix z^u*8bd-yf{9DWKvgzv+{fgT(V&P@Ag6ao-{00bZa0SG_<0uX=z1paSDSC;&P4s=m+C(E@XR~(T1lknP}T>sYZblbzNXHU4Bjq0Lwg|TvD`2L_YP&{U+gF+V{m(ZC)1HxFqh+Sth)ZHowRpKicEXxHadkga>mD6W)&BtuAMm z_p)-8Nxty6v8KD6N~a}Xj75Am7AJfu;mdCxobWK=D>tWnb;{QqUr+dkjmYa1WM@IT z<_q76cB5Suky5Xyvc<$GH49!xwaXM4Jt`^p8&@5Q$Q}D{FdzT{2tWV=5P$##AOHaf lKmY;|IOhUCeqlfW0uX=z1Rwwb2tWV=5P$##AaLFVegp6zdq4mH diff --git a/db.sqlite3 b/db.sqlite3 index d7961c895c28cd04df232a810837dd24df8b64b8..a84e999a9413b99058ece05b2cb646c4f3099e35 100644 GIT binary patch delta 1058 zcmZ9KOH30%7{_l4z?jbez-V_x&>8H}jw6w{^>J>mGaM z?iF8D?%tSqf2@%xQ13;gmyjUASk*P&*LGZ>NJoaob8W{PDqL078R5^{PF1R^im1W( zmo_L+tTU$bcJB#+LQHj)U-R}>Qp_-Akgs`hWixCLL+9VT9fGQ3l``(}DFUS!VV$4x zF@X}MYM4LrA>mk}V#YuEG(lB$iV1J=8zO}isyyb`1&T4z5P#|K6DXogqkPXV-T@&* zIc+C`3JpXMzuS&PTFQa&9nnRMqEb4JfN(R!AjBHK7!Y0o8JKAXzyF_G4wCVAfsjDyfdu^D)pYMfo}S=-FVCTHz}HB;X5XlT@P z$k|h!$i%ZLD;7`8q_eSUE4B41G#=h|g&q5W&Bw0XVHvn3;NB?yIaF)|5U}-&-lJt^N$lVRxvtsqo!Nwn6zfIh5X`^sNA}}6P3?B+;TnL54^bIfSckY4$9j;ut G6#5MuJ`?}| delta 2327 zcmb7EO>7&-72f4iktmxSxe+47lq^9%Xk3BR%MkyzgR+12aaRE}Vtu;vF>VGqiQ1d8T0_e>9pSqnBd-M$_au>xYv z*WI`$800WEKXOlYQ&|Z^i#j|y(SwQ*3Zl6; z&6mc+9+VRxE10PbV{u*ZpR7qDRdcKj7j*H2w%I7aubmAA?#yY$jc#C#aBs+ycu5`p~ORz{SY?24AurK4m#T?_3TuIY89 z*;2M8Sq`a4U#-U%LQiKY`dI9hB!Upz$+BCewco4{BwolNYlaZ|D!F_?*{^2Q=aD%m zSxaNkYG?+0NP*Ao;1lqn)vE6T*vA06#C(gnPWKknHo%NHI?#pLI~4c__%nFNvic41 z0A`Br>K$f?nW4K2Z3E1V;|p;Qp3KpML$v3lf1aM`_gvDbzYTo%wf4i4-_O(Y4%bNk z$m}frgZRlGw&~#q-;wEx?ZI0CI{3rOcJPP-{{jC7e+Q4OxPJoQ?qg`W-`=^I&TX?Z zw4?uAq8&V@z!UH}_$PR5kv{-uk6(Xr=*K1dWxMNb3cLw|fN{Nj+Wv*B<+nR1Ak$Z9 zhm1BfYfM$G64z=qeLp*4?B=y}ZQ>Pl-FExc{x6GZjSlf4!N+o5$Rk#Qe&+x7(P?2s zd@WY+3L=XL%Gx?kZhw=IyOZghwX8IC(l>E;GNWq+wQ4Uz> i^gZC6V#1DZs?mkb#}xS3viT#pHpa+~FZ%M?2>%68zh&hB diff --git a/project/migrations/0001_initial.py b/project/migrations/0001_initial.py index 3d0c7be..5ffe606 100644 --- a/project/migrations/0001_initial.py +++ b/project/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.3 on 2023-09-01 18:51 +# Generated by Django 4.2.3 on 2023-09-04 07:14 from django.db import migrations, models diff --git a/project/migrations/0002_initial.py b/project/migrations/0002_initial.py index 63b4258..f92b316 100644 --- a/project/migrations/0002_initial.py +++ b/project/migrations/0002_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.3 on 2023-09-01 18:51 +# Generated by Django 4.2.3 on 2023-09-04 07:14 from django.conf import settings from django.db import migrations, models @@ -10,8 +10,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('project', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('project', '0001_initial'), ] operations = [ diff --git a/project/tests.py b/project/tests.py index f625cca..2d146e5 100644 --- a/project/tests.py +++ b/project/tests.py @@ -19,7 +19,7 @@ def setUp(self): """Setup test data for Supervisor login tests.""" self.client = APIClient() self.user = CustomUser.objects.create_user(email='test@example.com', password='testpassword', username='test', - father_name='father', description='hell no') + father_name='father', description='hell no', country="Algeria") self.supervisor = Supervisor.objects.create(user=self.user, expertise="Testing") def test_supervisor_login(self): @@ -44,7 +44,8 @@ def setUp(self): """Setup test data for Comment view set tests.""" self.client = APIClient() self.user = CustomUser.objects.create_user(email='test3@example.com', password='testpassword', username='test3', - father_name='father3', description='description 3') + father_name='father3', description='description 3', + country="Algeria") self.supervisor = Supervisor.objects.create(user=self.user, expertise="QA") self.project = Project.objects.create(name="Test Project 2", description="A second test project", start_date="2023-01-02", end_date="2023-12-31", @@ -72,7 +73,7 @@ class ProjectSerializerTestCase(TestCase): def setUp(self): """Setup test data for Project serializer tests.""" self.user = CustomUser.objects.create(email="test@example.com", password="testpassword", username="testuser", - father_name="testfather", description="testdesc") + father_name="testfather", description="testdesc", country="Algeria") self.supervisor = Supervisor.objects.create(user=self.user) self.project_data = { 'name': 'Test Project', @@ -121,7 +122,7 @@ class CommentSerializerTestCase(TestCase): def setUp(self): """Setup test data for Comment serializer tests.""" self.user = CustomUser.objects.create(email="test2@example.com", password="testpassword2", username="testuser2", - father_name="testfather2", description="testdesc2") + father_name="testfather2", description="testdesc2", country="Algeria") self.supervisor = Supervisor.objects.create(user=self.user) self.project = Project.objects.create(name='Test Project', description='Test', start_date='2023-01-01', end_date='2023-12-31', supervisor=self.supervisor) @@ -159,7 +160,7 @@ def setUp(self): """Setup test data for Supervisor login serializer tests.""" self.user = CustomUser.objects.create_user(email="supervisor@example.com", password="supervisorpassword", username="supervisoruser", father_name="supervisorfather", - description="supervisordesc") + description="supervisordesc", country="Algeria") self.supervisor = Supervisor.objects.create(user=self.user) def test_valid_login(self): @@ -183,7 +184,7 @@ def test_invalid_login(self): def test_non_supervisor_login(self): user = CustomUser.objects.create_user(email="notasupervisor@example.com", password="testpassword3", username="nonsupervisoruser", father_name="nonsupervisorfather", - description="nonsupervisordesc") + description="nonsupervisordesc", country='Algeria') data = { 'email': 'notasupervisor@example.com', 'password': 'testpassword3' @@ -201,7 +202,7 @@ class ProjectsListViewTestCase(TestCase): def setUp(self): """Setup test data for ProjectsListView tests.""" self.user = CustomUser.objects.create_user(email='test@example.com', password='testpassword', username='test', - father_name='father', description='description') + father_name='father', description='description', country='Algeria') self.supervisor = Supervisor.objects.create(user=self.user, expertise="Testing") self.project1 = Project.objects.create(name="Test Project 1", supervisor=self.supervisor, start_date=date.today(), end_date=date.today() + timedelta(days=10)) @@ -229,7 +230,9 @@ def setUp(self): """Setup test data for ViewCommentsView tests.""" self.user = CustomUser.objects.create_user(email='commenter@example.com', password='testpassword', username='commenter', - father_name='father_commenter', description='description_commenter') + father_name='father_commenter', description='description_commenter', + country='Algeria' + ) self.supervisor = Supervisor.objects.create(user=self.user, expertise="QA") self.project = Project.objects.create(name="Test Project", supervisor=self.supervisor, start_date=date.today(), end_date=date.today() + timedelta(days=10)) diff --git a/user/forms.py b/user/forms.py index 332187f..70e3813 100644 --- a/user/forms.py +++ b/user/forms.py @@ -2,7 +2,7 @@ from django.contrib.auth.forms import UserCreationForm from .models import CustomUser from utils.constants import USER_FORM_FIELDS -from utils.helpers import validate_password +from utils.helpers import validate_password, get_countries class CustomUserCreationForm(UserCreationForm): @@ -12,6 +12,12 @@ class CustomUserCreationForm(UserCreationForm): This form inherits from Django's built-in UserCreationForm and adds additional fields for the CustomUser. """ + country = forms.ChoiceField(choices=[]) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + countries = get_countries() + self.fields['country'].choices = [(country, country) for country in countries] class Meta: model = CustomUser diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py index 5a03f0a..042058e 100644 --- a/user/migrations/0001_initial.py +++ b/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.3 on 2023-09-01 19:15 +# Generated by Django 4.2.3 on 2023-09-04 07:14 import django.contrib.auth.models import django.contrib.auth.validators @@ -28,7 +28,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('duration', models.PositiveIntegerField(default=30)), - ('duration_type', models.CharField(choices=[('seconds', 'Seconds'), ('minutes', 'Minutes'), ('hours', 'Hours')], default='minutes', max_length=7)), + ('duration_type', models.CharField(choices=[('seconds', 'Seconds'), ('minutes', 'Minutes'), ('hours', 'Hours'), ('days', 'Days')], default='minutes', max_length=7)), ], options={ 'verbose_name_plural': 'Reminder Settings', @@ -52,6 +52,7 @@ class Migration(migrations.Migration): ('description', models.TextField(blank=True, null=True)), ('software_engineering_experience', models.PositiveIntegerField(blank=True, null=True)), ('last_profile_update', models.DateTimeField(blank=True, null=True)), + ('country', models.CharField(max_length=100)), ('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')), ], diff --git a/user/models.py b/user/models.py index 00085c1..cbca6d8 100644 --- a/user/models.py +++ b/user/models.py @@ -4,7 +4,6 @@ from utils.helpers import update_user_profile_fields - class CustomUser(AbstractUser): """ Custom user model that extends Django's built-in AbstractUser class. @@ -18,6 +17,7 @@ class CustomUser(AbstractUser): description = models.TextField(null=True, blank=True) software_engineering_experience = models.PositiveIntegerField(null=True, blank=True) last_profile_update = models.DateTimeField(null=True, blank=True) + country = models.CharField(max_length=100) REQUIRED_FIELDS = REQUIRED_USER_FIELDS diff --git a/user/serializers.py b/user/serializers.py index 025d236..09f36c1 100644 --- a/user/serializers.py +++ b/user/serializers.py @@ -1,19 +1,27 @@ from rest_framework import serializers from .models import CustomUser -from utils.helpers import validate_password +from utils.helpers import validate_password, get_countries class CustomUserSerializer(serializers.ModelSerializer): class Meta: model = CustomUser - fields = ['id','username', 'email', 'father_name', 'description', 'software_engineering_experience', 'last_profile_update'] + fields = ['id', 'username', 'email', 'father_name', 'description', 'software_engineering_experience', + 'last_profile_update', 'country'] + class CustomUserRegistrationSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True, required=True, validators=[validate_password]) + country = serializers.ChoiceField(choices=get_countries()) class Meta: model = CustomUser - fields = ['username','email', 'password', 'father_name', 'description', 'software_engineering_experience'] + fields = ['username', 'email', 'password', 'father_name', 'description', 'software_engineering_experience', + 'country'] + + def __init__(self, *args, **kwargs): + super(CustomUserRegistrationSerializer, self).__init__(*args, **kwargs) + self.fields['country'].choices = [(country, country) for country in get_countries()] def create(self, validated_data): user = CustomUser.objects.create_user( @@ -22,10 +30,12 @@ def create(self, validated_data): password=validated_data['password'], father_name=validated_data['father_name'], description=validated_data.get('description', None), - software_engineering_experience=validated_data.get('software_engineering_experience', None) + software_engineering_experience=validated_data.get('software_engineering_experience', None), + country=validated_data.get('country', None), ) return user + class ChangePasswordSerializer(serializers.Serializer): old_password = serializers.CharField(required=True) new_password = serializers.CharField(required=True, validators=[validate_password]) diff --git a/user/tasks.py b/user/tasks.py index 6b4f3e2..7be90cd 100644 --- a/user/tasks.py +++ b/user/tasks.py @@ -7,7 +7,6 @@ from .models import CustomUser, ReminderSetting - @shared_task(bind=True) def send_welcome_email(self, email, username): try: @@ -57,5 +56,4 @@ def check_last_login_and_send_email(self): subject = "We appreciate you!" message = f"Hello {user.username},\n\n We have noticed that its been a while since you last visited. We would love to see you back on our platform!" - send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email]) diff --git a/user/tests.py b/user/tests.py index 70dc449..bdd083b 100644 --- a/user/tests.py +++ b/user/tests.py @@ -100,7 +100,8 @@ def setUp(self): 'email': 'test112234@example.com', 'father_name': 'testfather', 'description': 'test description', - 'software_engineering_experience': 2 + 'software_engineering_experience': 2, + 'country': 'Algeria' } self.user = CustomUser.objects.create(**self.user_attributes) self.serializer = CustomUserSerializer(instance=self.user) @@ -110,7 +111,7 @@ def test_successful_serialization(self): data = self.serializer.data self.assertEqual(set(data.keys()), {'id', 'username', 'email', 'father_name', 'description', 'software_engineering_experience', - 'last_profile_update'}) + 'last_profile_update', 'country'}) self.assertEqual(data['username'], self.user_attributes['username']) def test_successful_deserialization(self): @@ -136,7 +137,8 @@ def setUp(self): 'password': 'password123', 'father_name': 'testfather', 'description': 'test description', - 'software_engineering_experience': 2 + 'software_engineering_experience': 2, + 'country': 'Algeria' } def test_successful_serialization(self): @@ -197,7 +199,8 @@ def setUp(self): 'password': 'testpass123', 'email': 'testuser@example.com', 'father_name': 'testfather', - 'description': 'Test description' + 'description': 'Test description', + 'country': 'Algeria' } self.user = get_user_model().objects.create_user(**self.user_data) self.client.login(email=self.user_data['email'], password=self.user_data['password']) @@ -228,7 +231,8 @@ def test_edit_profile_post(self): 'username': 'updateduser', 'email': 'updateduser@example.com', 'father_name': 'updatedfather', - 'description': 'Updated description' + 'description': 'Updated description', + 'country': 'Updated Country' }) updated_user = get_user_model().objects.get(pk=self.user.pk) @@ -237,6 +241,7 @@ def test_edit_profile_post(self): self.assertEqual(updated_user.email, 'updateduser@example.com') self.assertEqual(updated_user.father_name, 'updatedfather') self.assertEqual(updated_user.description, 'Updated description') + self.assertEqual(updated_user.country, 'Updated Country') self.assertRedirects(response, reverse('home')) @@ -254,6 +259,7 @@ def setUp(self): self.father_name = "John Doe" self.description = "Test description" self.software_engineering_experience = 5 + self.country = "Algeria" self.user = CustomUser.objects.create_user( username=self.test_username, @@ -261,7 +267,8 @@ def setUp(self): password=self.test_password, father_name=self.father_name, description=self.description, - software_engineering_experience=self.software_engineering_experience + software_engineering_experience=self.software_engineering_experience, + country=self.country ) def test_signup_api(self): @@ -272,7 +279,8 @@ def test_signup_api(self): 'password': 'password123', 'father_name': 'John Doe', 'description': 'Just a test user.', - 'software_engineering_experience': 2 + 'software_engineering_experience': 2, + 'country': 'Algeria' } response = self.client.post(reverse('signup-api'), data) @@ -317,7 +325,8 @@ def setUp(self): 'password': 'password123', 'father_name': 'John Doe', 'description': 'Just a test user.', - 'software_engineering_experience': 2 + 'software_engineering_experience': 2, + 'country': 'Algeria' } self.user = CustomUser.objects.create_user(**self.user_data) diff --git a/user/urls.py b/user/urls.py index c20b2bd..1c74057 100644 --- a/user/urls.py +++ b/user/urls.py @@ -1,14 +1,16 @@ from django.urls import path -from .views import SignupView, LoginView, LogoutView, EditProfileView, ChangePasswordView, HomeView, ListUsersView, SignupAPIView, LoginAPIView,logout_api_view, EditProfileAPIView,ChangePasswordAPIView,ListUsersAPIView +from .views import SignupView, LoginView, LogoutView, EditProfileView, ChangePasswordView, HomeView, ListUsersView, \ + SignupAPIView, LoginAPIView, logout_api_view, EditProfileAPIView, ChangePasswordAPIView, ListUsersAPIView from .views import CustomTokenRefreshView + # URL patterns for the user management functionalities urlpatterns = [ - path('login/', LoginView.as_view(), name='login'), # URL pattern for user login - path('signup/', SignupView.as_view(), name='signup'), # URL pattern for user registration - path('logout/', LogoutView.as_view(), name='logout'), # URL pattern for user logout + path('login/', LoginView.as_view(), name='login'), # URL pattern for user login + path('signup/', SignupView.as_view(), name='signup'), # URL pattern for user registration + path('logout/', LogoutView.as_view(), name='logout'), # URL pattern for user logout path('edit_profile/', EditProfileView.as_view(), name='edit_profile'), # URL pattern for editing user profiles path('change_password/', ChangePasswordView.as_view(), name='change_password'), # URL for changing user passwords - path('home/', HomeView.as_view(), name='home'), # URL pattern for rendering the home page + path('home/', HomeView.as_view(), name='home'), # URL pattern for rendering the home page path('api/users/', ListUsersView.as_view(), name='list-users'), path('api/signup/', SignupAPIView.as_view(), name='signup-api'), diff --git a/user/views.py b/user/views.py index 0204bdf..a0eb5ce 100644 --- a/user/views.py +++ b/user/views.py @@ -16,7 +16,7 @@ from .serializers import (CustomUserSerializer, CustomUserRegistrationSerializer, ChangePasswordSerializer) from rest_framework.permissions import AllowAny from .tasks import send_welcome_email - +from utils.helpers import get_countries from utils.constants import ( SIGNUP_TEMPLATE, LOGIN_TEMPLATE, @@ -38,11 +38,16 @@ class SignupView(View): @staticmethod def get(request): form = CustomUserCreationForm() + countries = get_countries() # Fetch countries from cache + form.fields['country'].choices = [(country, country) for country in countries] # Set the country choices return render(request, SIGNUP_TEMPLATE, {'form': form}) @staticmethod def post(request): form = CustomUserCreationForm(request.POST) + countries = get_countries() # Fetch countries from cache + form.fields['country'].choices = [(country, country) for country in countries] # Set the country choices + response = validate_and_save_form(form, request, 'login', SIGNUP_TEMPLATE, VALIDATION_ERROR_MSG) if form.is_valid(): send_welcome_email.delay(email=form.cleaned_data.get('email'), diff --git a/utils/constants.py b/utils/constants.py index 144d030..1908c1b 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -18,16 +18,19 @@ ] # Constants related to the CustomUser model -REQUIRED_USER_FIELDS = ['father_name', 'email', 'description'] +REQUIRED_USER_FIELDS = ['father_name', 'email', 'description', 'country'] DEFAULT_SOFTWARE_ENGINEERING_EXPERIENCE = 0 + # Constants related to the CustomUser forms USER_FORM_FIELDS = ( 'email', 'username', 'father_name', 'software_engineering_experience', - 'description' + 'description', + 'country' + ) PASSWORD_MIN_LENGTH = 6 PASSWORD_DIGIT_MESSAGE = 'Password must contain at least 1 number.' diff --git a/utils/helpers.py b/utils/helpers.py index 081fa0a..ec3b65e 100644 --- a/utils/helpers.py +++ b/utils/helpers.py @@ -1,7 +1,10 @@ from django.shortcuts import render, redirect from django.core.exceptions import ValidationError from django.utils import timezone -from utils.constants import PROTECTED_VIEW_NAMES, DEFAULT_SOFTWARE_ENGINEERING_EXPERIENCE, PASSWORD_MIN_LENGTH, PASSWORD_DIGIT_MESSAGE, PASSWORD_LENGTH_MESSAGE +from utils.constants import PROTECTED_VIEW_NAMES, DEFAULT_SOFTWARE_ENGINEERING_EXPERIENCE, PASSWORD_MIN_LENGTH, \ + PASSWORD_DIGIT_MESSAGE, PASSWORD_LENGTH_MESSAGE +from django.core.cache import cache +from django_countries import countries def render_with_error(request, template, form, error_msg): @@ -52,3 +55,14 @@ def validate_password(password): if not any(char.isdigit() for char in password): raise ValidationError(PASSWORD_DIGIT_MESSAGE) return password + + +def get_countries(): + countries_list = cache.get('countries_list') + + if not countries_list: + # This is just a sample list. Use a comprehensive one or fetch it from a source. + countries_list = [country[1] for country in countries] + cache.set('countries_list', countries_list, 86400) # Cache for 24 hours + + return countries_list From 5cb0a2049d5f8555fc1ef29bfb751b6b3c440127 Mon Sep 17 00:00:00 2001 From: Abdul-Muqadim-Arbisoft Date: Mon, 4 Sep 2023 14:45:23 +0500 Subject: [PATCH 3/4] Readem updated --- README.md | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9c75ef0..8d93a21 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ A Django-based project management system that provides functionalities for proje - [Features App Wise](#features-app-wise) - [Management Commands](#management-commands) - [Django Admin Enhancements](#django-admin-enhancements) +- [Celery Tasks](#celery-tasks) +- [Unit Tests](#unit-test) - [How to Use](#how-to-use) - [Dependencies](#dependencies) - [Future Enhancements](#future-enhancements) @@ -37,6 +39,12 @@ muqadim_basic_user_app_django/ │ │ ├── projects_list.html │ │ ├── supervisor_login.html │ │ └── view_comments.html +│ ├── 📁 static/ +│ │ └── css/ +│ │ ├── create_project.css +│ │ ├── projects_list.css +│ │ ├── supervisor_login.css +│ │ └── view_comments.css │ ├── __init__.py │ ├── admin.py │ ├── apps.py @@ -53,6 +61,7 @@ muqadim_basic_user_app_django/ │ ├── settings.py │ ├── urls.py │ └── wsgi.py +│ ├── celery.py │ ├── 📁 user/ │ ├── 📁 management/ @@ -70,6 +79,13 @@ muqadim_basic_user_app_django/ │ │ ├── home.html │ │ ├── login.html │ │ └── signup.html +│ ├── 📁 static/ +│ │ └── css/ +│ │ ├── change_password.css +│ │ ├── edit_profile.css +│ │ ├── home.css +│ │ ├── login.css +│ │ └── signup.css │ ├── __init__.py │ ├── admin.py │ ├── admin_forms.py @@ -82,6 +98,7 @@ muqadim_basic_user_app_django/ │ ├── serializers.py │ ├── tests.py │ ├── urls.py +│ ├── tasks.py │ └── views.py │ ├── 📁 utils/ @@ -328,8 +345,62 @@ The app's URL blueprint provides paths for both conventional web views and API a Models like `CustomUser`, `DateTimeRecord`,`PROJECT`,`SUPERVISOR`,`COMMENTS` etc are registered. Customizations include field rearrangements, list displays, and filters. -## How to Use +## Celery-Tasks +1. Whenever a user signs up a welcome mail is sent to him ,MailTral service is being used for this purpose,you need to run redis server on one terminal and celery server in other as well for this purpose +#### For starting redis +```bash +redis server +``` + +### in other terminal +#### For starting the celery +```bash +celery -A UniManage worker --loglevel=info +``` +2. A reminder mail to get back to platform is sent to the user ,the period upon which this reminder mail is to be sent is decided in model reminder setting you can edit that value in the django admin,MailTral service is being used for this purpose,you need to run redis server ion one terminal and celery server in other and celery beat server on another terminal as well for this purpose +#### For starting redis +```bash +redis server +``` + +### in other terminal +#### For starting the celery +```bash +celery -A UniManage worker --loglevel=info +``` + +### in another terminal +#### For starting the celery beat +```bash +celery -A UniManage beat --loglevel=info +``` + +## Unit-Test +Unit Tests have been deployed for both models ,unit tests dont cover the whole project, total coverage of unit test for this whole project is 42% . +### You can run the unit tests through following commands +#### For User app +```bash +python3 manage.py test user +``` +#### For Project app +```bash +python3 manage.py test project +``` +#### For whole project +```bash +python3 manage.py test +``` +#### For Unit Test Coverage +```bash +coverage run manage.py test +``` +```bash +coverage report +``` + + +## How to Use 1. Set up a Django project. 2. Ensure Django's authentication system is set up. 3. Include the URL patterns in your project's configuration. @@ -393,18 +464,28 @@ python manage.py runserver Yet another Swagger generator. It's a great tool for creating API documentation with OpenAPI and Swagger. +- **redis** + + Redis is an open-source, in-memory data structure store, used as a database, cache, and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, and geospatial indexes with radius queries. + +- **celery** + + Celery is an asynchronous task queue/job queue based on distributed message passing. It is focused on real-time operation but supports scheduling as well. + +- **celery[beat]** + + Celery Beat is a scheduler that integrates with Celery. It allows you to run tasks at regular intervals, similar to cron jobs in Unix systems. + +- **django-countries** + + A Django application that provides a country field for models and forms. It allows you to easily add drop-downs in forms to select a country, and it includes a list of all countries with their names, ISO codes, and more. + To install all the dependencies, use the following pip command: ```bash -pip install django==4.2.3 djangorestframework django-rest-framework-simplejwt djoser drf-yasg +pip install django==4.2.3 djangorestframework django-rest-framework-simplejwt djoser drf-yasg redis celery celery[beat] django-countries ``` ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. - -celery command: - /Users/abdul.muqadim/Library/Python/3.9/bin/celery -A UniManage worker --loglevel=info - -/Users/abdul.muqadim/Library/Python/3.9/bin/celery -A UniManage worker --loglevel=info -/Users/abdul.muqadim/Library/Python/3.9/bin/celery -A UniManage beat --loglevel=info From 56374d1e51d282db95445efacef406262af35cbe Mon Sep 17 00:00:00 2001 From: Abdul-Muqadim-Arbisoft Date: Sat, 7 Oct 2023 20:37:33 +0500 Subject: [PATCH 4/4] git revions made by removing index 1 and converting it to constant file --- utils/constants.py | 4 ++-- utils/helpers.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/constants.py b/utils/constants.py index 1908c1b..c1f6479 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -1,4 +1,3 @@ - # Constants related to URLs/templates paths SIGNUP_TEMPLATE = 'user/signup.html' LOGIN_TEMPLATE = 'user/login.html' @@ -21,7 +20,6 @@ REQUIRED_USER_FIELDS = ['father_name', 'email', 'description', 'country'] DEFAULT_SOFTWARE_ENGINEERING_EXPERIENCE = 0 - # Constants related to the CustomUser forms USER_FORM_FIELDS = ( 'email', @@ -112,3 +110,5 @@ # Name for the token refresh endpoint TOKEN_REFRESH_NAME = 'token_refresh' + +COUNTRY_NAME_INDEX = 1; diff --git a/utils/helpers.py b/utils/helpers.py index ec3b65e..4a50643 100644 --- a/utils/helpers.py +++ b/utils/helpers.py @@ -2,7 +2,7 @@ from django.core.exceptions import ValidationError from django.utils import timezone from utils.constants import PROTECTED_VIEW_NAMES, DEFAULT_SOFTWARE_ENGINEERING_EXPERIENCE, PASSWORD_MIN_LENGTH, \ - PASSWORD_DIGIT_MESSAGE, PASSWORD_LENGTH_MESSAGE + PASSWORD_DIGIT_MESSAGE, PASSWORD_LENGTH_MESSAGE, COUNTRY_NAME_INDEX from django.core.cache import cache from django_countries import countries @@ -62,7 +62,7 @@ def get_countries(): if not countries_list: # This is just a sample list. Use a comprehensive one or fetch it from a source. - countries_list = [country[1] for country in countries] + countries_list = [country[COUNTRY_NAME_INDEX] for country in countries] cache.set('countries_list', countries_list, 86400) # Cache for 24 hours return countries_list