11 | {% blocktranslate %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktranslate %}
12 |
12 | {% blocktranslate %}This part of the site requires us to verify that
13 | you are who you claim to be. For this purpose, we require that you
14 | verify ownership of your e-mail address. {% endblocktranslate %}
15 |
16 |
17 | {% blocktranslate %}We have sent an e-mail to you for
18 | verification. Please click on the link inside this e-mail. Please
19 | contact us if you do not receive it within a few minutes.{% endblocktranslate %}
20 |
24 | {% endblock inner %}
25 |
--------------------------------------------------------------------------------
/Phase_3/README.md:
--------------------------------------------------------------------------------
1 | # Resume Builder
2 |
3 |
4 | Place this code at `.env` file:
5 |
6 | ```python
7 | # General
8 | # ------------------------------------------------------------------------------
9 | USE_DOCKER=yes
10 | IPYTHONDIR=/app/.ipython
11 |
12 | # PostgreSQL
13 | # ------------------------------------------------------------------------------
14 | POSTGRES_HOST=postgres
15 | POSTGRES_PORT=5432
16 | POSTGRES_DB=resume_builder
17 | POSTGRES_USER=debug
18 | POSTGRES_PASSWORD=debug
19 | ```
20 |
21 | make sure that change POSTGRES_DB and POSTGRES_PASSWORD base on your config, after that,
22 | simply run
23 |
24 | ```python
25 | pip install -r requirements/local.txt
26 | ```
27 | python manage.py migrate
28 | and then:
29 | python manage.py runserver
30 |
31 | make sure about doing all parts, after that you will find swagger of the project at
32 | http://127.0.0.1/swagger/ and api root at the
33 | http://127.0.0.1/api/.
34 |
35 | to run docker run docker-compose up
36 | to build docker image run docker buid -t onlinetaskbar:latest
37 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/users/api/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from rest_framework import status
3 | from rest_framework.decorators import action
4 | from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
5 | from rest_framework.response import Response
6 | from rest_framework.viewsets import GenericViewSet
7 |
8 | from .serializers import UserSerializer
9 |
10 | User = get_user_model()
11 |
12 |
13 | class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet):
14 | serializer_class = UserSerializer
15 | queryset = User.objects.all()
16 | lookup_field = "pk"
17 |
18 | def get_queryset(self, *args, **kwargs):
19 | assert isinstance(self.request.user.id, int)
20 | return self.queryset.filter(id=self.request.user.id)
21 |
22 | @action(detail=False)
23 | def me(self, request):
24 | serializer = UserSerializer(request.user, context={"request": request})
25 | return Response(status=status.HTTP_200_OK, data=serializer.data)
26 |
--------------------------------------------------------------------------------
/Phase_3/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = ./_build
10 | APP = /app
11 |
12 | .PHONY: help livehtml apidocs Makefile
13 |
14 | # Put it first so that "make" without argument is like "make help".
15 | help:
16 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -c .
17 |
18 | # Build, watch and serve docs with live reload
19 | livehtml:
20 | sphinx-autobuild -b html --host 0.0.0.0 --port 9000 --watch $(APP) -c . $(SOURCEDIR) $(BUILDDIR)/html
21 |
22 | # Outputs rst files from django application code
23 | apidocs:
24 | sphinx-apidoc -o $(SOURCEDIR)/api $(APP)
25 |
26 | # Catch-all target: route all unknown targets to Sphinx using the new
27 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
28 | %: Makefile
29 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -c .
30 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/resume/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 | from .models import Education, Experience, Language, Project, Resume, Skill
4 |
5 |
6 | class LanguageSerializer(serializers.ModelSerializer):
7 | class Meta:
8 | model = Language
9 | fields = "__all__"
10 |
11 |
12 | class ProjectSerializer(serializers.ModelSerializer):
13 | class Meta:
14 | model = Project
15 | fields = "__all__"
16 |
17 |
18 | class SkillSerializer(serializers.ModelSerializer):
19 | class Meta:
20 | model = Skill
21 | fields = "__all__"
22 |
23 |
24 | class ExperienceSerializer(serializers.ModelSerializer):
25 | class Meta:
26 | model = Experience
27 | fields = "__all__"
28 |
29 |
30 | class EducationSerializer(serializers.ModelSerializer):
31 | class Meta:
32 | model = Education
33 | fields = "__all__"
34 |
35 |
36 | class ResumeSerializer(serializers.ModelSerializer):
37 | class Meta:
38 | model = Resume
39 | fields = "__all__"
40 |
--------------------------------------------------------------------------------
/Phase_3/tests/test_merge_production_dotenvs_in_dotenv.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import pytest
4 |
5 | from merge_production_dotenvs_in_dotenv import merge
6 |
7 |
8 | @pytest.mark.parametrize(
9 | ("input_contents", "expected_output"),
10 | [
11 | ([], ""),
12 | ([""], "\n"),
13 | (["JANE=doe"], "JANE=doe\n"),
14 | (["SEP=true", "AR=ator"], "SEP=true\nAR=ator\n"),
15 | (["A=0", "B=1", "C=2"], "A=0\nB=1\nC=2\n"),
16 | (["X=x\n", "Y=y", "Z=z\n"], "X=x\n\nY=y\nZ=z\n\n"),
17 | ],
18 | )
19 | def test_merge(
20 | tmp_path: Path,
21 | input_contents: list[str],
22 | expected_output: str,
23 | ):
24 | output_file = tmp_path / ".env"
25 |
26 | files_to_merge = []
27 | for num, input_content in enumerate(input_contents, start=1):
28 | merge_file = tmp_path / f".service{num}"
29 | merge_file.write_text(input_content)
30 | files_to_merge.append(merge_file)
31 |
32 | merge(output_file, files_to_merge)
33 |
34 | assert output_file.read_text() == expected_output
35 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/templates/account/password_reset.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account %}
5 | {% load crispy_forms_tags %}
6 |
7 | {% block head_title %}
8 | {% translate "Password Reset" %}
9 | {% endblock head_title %}
10 | {% block inner %}
11 |
{% translate "Password Reset" %}
12 | {% if user.is_authenticated %}
13 | {% include "account/snippets/already_logged_in.html" %}
14 | {% endif %}
15 |
16 | {% translate "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}
17 |
18 |
27 |
{% blocktranslate %}Please contact us if you have any trouble resetting your password.{% endblocktranslate %}
28 | {% endblock inner %}
29 |
--------------------------------------------------------------------------------
/Phase_3/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 | Copyright (c) 2024, Alireza Parvaresh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/users/tests/test_drf_views.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from rest_framework.test import APIRequestFactory
3 |
4 | from resume_builder.users.api.views import UserViewSet
5 | from resume_builder.users.models import User
6 |
7 |
8 | class TestUserViewSet:
9 | @pytest.fixture
10 | def api_rf(self) -> APIRequestFactory:
11 | return APIRequestFactory()
12 |
13 | def test_get_queryset(self, user: User, api_rf: APIRequestFactory):
14 | view = UserViewSet()
15 | request = api_rf.get("/fake-url/")
16 | request.user = user
17 |
18 | view.request = request
19 |
20 | assert user in view.get_queryset()
21 |
22 | def test_me(self, user: User, api_rf: APIRequestFactory):
23 | view = UserViewSet()
24 | request = api_rf.get("/fake-url/")
25 | request.user = user
26 |
27 | view.request = request
28 |
29 | response = view.me(request) # type: ignore
30 |
31 | assert response.data == {
32 | "url": f"http://testserver/api/users/{user.pk}/",
33 | "name": user.name,
34 | }
35 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/static/css/column_scroll.css:
--------------------------------------------------------------------------------
1 | #left , #right {
2 | overflow: auto;
3 | padding: 0;
4 | }
5 | #right {
6 | right: 0;
7 | }
8 | @media(min-width:768px)
9 | {
10 | #left , #right {
11 | position: absolute;
12 | margin-top: 0px;
13 | top: 0;
14 | bottom: 0;
15 | }
16 | }
17 |
18 | @media print {
19 | .no-print, .no-print * { display: none !important; }
20 |
21 | #left , #right {
22 | overflow: initial;
23 | min-height: initial;
24 | height: initial;
25 | position: relative;
26 | margin-top: 0px;
27 | }
28 |
29 | .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { float: left; }
30 | .col-sm-12 { width: 100%; }
31 | .col-sm-11 { width: 91.66666667%; }
32 | .col-sm-10 { width: 83.33333333%; }
33 | .col-sm-9 { width: 75%; }
34 | .col-sm-8 { width: 66.66666667%; }
35 | .col-sm-7 { width: 58.33333333%; }
36 | .col-sm-6 { width: 50%; }
37 | .col-sm-5 { width: 41.66666667%; }
38 | .col-sm-4 { width: 33.33333333%; }
39 | .col-sm-3 { width: 25%; }
40 | .col-sm-2 { width: 16.66666667%; }
41 | .col-sm-1 { width: 8.33333333%; }
42 | }
--------------------------------------------------------------------------------
/Phase_3/compose/production/postgres/maintenance/rmbackup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ### Remove a database backup.
4 | ###
5 | ### Parameters:
6 | ### <1> filename of a backup to remove.
7 | ###
8 | ### Usage:
9 | ### $ docker-compose -f .yml (exec |run --rm) postgres rmbackup <1>
10 |
11 |
12 | set -o errexit
13 | set -o pipefail
14 | set -o nounset
15 |
16 |
17 | working_dir="$(dirname ${0})"
18 | source "${working_dir}/_sourced/constants.sh"
19 | source "${working_dir}/_sourced/messages.sh"
20 |
21 |
22 | if [[ -z ${1+x} ]]; then
23 | message_error "Backup filename is not specified yet it is a required parameter. Make sure you provide one and try again."
24 | exit 1
25 | fi
26 | backup_filename="${BACKUP_DIR_PATH}/${1}"
27 | if [[ ! -f "${backup_filename}" ]]; then
28 | message_error "No backup with the specified filename found. Check out the 'backups' maintenance script output to see if there is one and try again."
29 | exit 1
30 | fi
31 |
32 | message_welcome "Removing the '${backup_filename}' backup file..."
33 |
34 | rm -r "${backup_filename}"
35 |
36 | message_success "The '${backup_filename}' database backup has been removed."
37 |
--------------------------------------------------------------------------------
/Phase_3/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 | from pathlib import Path
5 |
6 | if __name__ == "__main__":
7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
8 |
9 | try:
10 | from django.core.management import execute_from_command_line
11 | except ImportError:
12 | # The above import may fail for some other reason. Ensure that the
13 | # issue is really that Django is missing to avoid masking other
14 | # exceptions on Python 2.
15 | try:
16 | import django # noqa
17 | except ImportError:
18 | raise ImportError(
19 | "Couldn't import Django. Are you sure it's installed and "
20 | "available on your PYTHONPATH environment variable? Did you "
21 | "forget to activate a virtual environment?"
22 | )
23 |
24 | raise
25 |
26 | # This allows easy placement of apps within the interior
27 | # resume_builder directory.
28 | current_path = Path(__file__).parent.resolve()
29 | sys.path.append(str(current_path / "resume_builder"))
30 |
31 | execute_from_command_line(sys.argv)
32 |
--------------------------------------------------------------------------------
/Phase_3/compose/production/postgres/maintenance/backup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 |
4 | ### Create a database backup.
5 | ###
6 | ### Usage:
7 | ### $ docker compose -f .yml (exec |run --rm) postgres backup
8 |
9 |
10 | set -o errexit
11 | set -o pipefail
12 | set -o nounset
13 |
14 |
15 | working_dir="$(dirname ${0})"
16 | source "${working_dir}/_sourced/constants.sh"
17 | source "${working_dir}/_sourced/messages.sh"
18 |
19 |
20 | message_welcome "Backing up the '${POSTGRES_DB}' database..."
21 |
22 |
23 | if [[ "${POSTGRES_USER}" == "postgres" ]]; then
24 | message_error "Backing up as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again."
25 | exit 1
26 | fi
27 |
28 | export PGHOST="${POSTGRES_HOST}"
29 | export PGPORT="${POSTGRES_PORT}"
30 | export PGUSER="${POSTGRES_USER}"
31 | export PGPASSWORD="${POSTGRES_PASSWORD}"
32 | export PGDATABASE="${POSTGRES_DB}"
33 |
34 | backup_filename="${BACKUP_FILE_PREFIX}_$(date +'%Y_%m_%dT%H_%M_%S').sql.gz"
35 | pg_dump | gzip > "${BACKUP_DIR_PATH}/${backup_filename}"
36 |
37 |
38 | message_success "'${POSTGRES_DB}' database backup '${backup_filename}' has been created and placed in '${BACKUP_DIR_PATH}'."
39 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/templates/account/email_confirm.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account %}
5 |
6 | {% block head_title %}
7 | {% translate "Confirm E-mail Address" %}
8 | {% endblock head_title %}
9 | {% block inner %}
10 |
{% translate "Confirm E-mail Address" %}
11 | {% if confirmation %}
12 | {% user_display confirmation.email_address.user as user_display %}
13 |
14 | {% blocktranslate with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktranslate %}
15 |
17 | {% if token_fail %}
18 | {% url 'account_reset_password' as passwd_reset_url %}
19 |
20 | {% blocktranslate %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktranslate %}
21 |
22 | {% else %}
23 | {% if form %}
24 |
32 | {% else %}
33 |
{% translate "Your password is now changed." %}
34 | {% endif %}
35 | {% endif %}
36 | {% endblock inner %}
37 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/users/tests/test_forms.py:
--------------------------------------------------------------------------------
1 | """Module for all Form Tests."""
2 |
3 | from django.utils.translation import gettext_lazy as _
4 |
5 | from resume_builder.users.forms import UserAdminCreationForm
6 | from resume_builder.users.models import User
7 |
8 |
9 | class TestUserAdminCreationForm:
10 | """
11 | Test class for all tests related to the UserAdminCreationForm
12 | """
13 |
14 | def test_username_validation_error_msg(self, user: User):
15 | """
16 | Tests UserAdminCreation Form's unique validator functions correctly by testing:
17 | 1) A new user with an existing username cannot be added.
18 | 2) Only 1 error is raised by the UserCreation Form
19 | 3) The desired error message is raised
20 | """
21 |
22 | # The user already exists,
23 | # hence cannot be created.
24 | form = UserAdminCreationForm(
25 | {
26 | "email": user.email,
27 | "password1": user.password,
28 | "password2": user.password,
29 | }
30 | )
31 |
32 | assert not form.is_valid()
33 | assert len(form.errors) == 1
34 | assert "email" in form.errors
35 | assert form.errors["email"][0] == _("This email has already been taken.")
36 |
--------------------------------------------------------------------------------
/Phase_3/requirements/base.txt:
--------------------------------------------------------------------------------
1 | python-slugify==8.0.2 # https://github.com/un33k/python-slugify
2 | Pillow==10.2.0 # https://github.com/python-pillow/Pillow
3 | argon2-cffi==23.1.0 # https://github.com/hynek/argon2_cffi
4 | whitenoise==6.6.0 # https://github.com/evansd/whitenoise
5 | redis==5.0.1 # https://github.com/redis/redis-py
6 | hiredis==2.3.2 # https://github.com/redis/hiredis-py
7 |
8 | # Django
9 | # ------------------------------------------------------------------------------
10 | django==4.2.9 # pyup: < 5.0 # https://www.djangoproject.com/
11 | django-environ==0.11.2 # https://github.com/joke2k/django-environ
12 | django-model-utils==4.3.1 # https://github.com/jazzband/django-model-utils
13 | django-allauth==0.60.1 # https://github.com/pennersr/django-allauth
14 | django-crispy-forms==2.1 # https://github.com/django-crispy-forms/django-crispy-forms
15 | crispy-bootstrap5==2023.10 # https://github.com/django-crispy-forms/crispy-bootstrap5
16 | django-redis==5.4.0 # https://github.com/jazzband/django-redis
17 | # Django REST Framework
18 | djangorestframework==3.14.0 # https://github.com/encode/django-rest-framework
19 | django-cors-headers==4.3.1 # https://github.com/adamchainz/django-cors-headers
20 | # DRF-spectacular for api documentation
21 | drf-spectacular==0.27.1 # https://github.com/tfranzel/drf-spectacular
22 |
--------------------------------------------------------------------------------
/Phase_3/compose/production/django/entrypoint:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -o errexit
4 | set -o pipefail
5 | set -o nounset
6 |
7 |
8 |
9 |
10 | if [ -z "${POSTGRES_USER}" ]; then
11 | base_postgres_image_default_user='postgres'
12 | export POSTGRES_USER="${base_postgres_image_default_user}"
13 | fi
14 | export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
15 |
16 | python << END
17 | import sys
18 | import time
19 |
20 | import psycopg
21 |
22 | suggest_unrecoverable_after = 30
23 | start = time.time()
24 |
25 | while True:
26 | try:
27 | psycopg.connect(
28 | dbname="${POSTGRES_DB}",
29 | user="${POSTGRES_USER}",
30 | password="${POSTGRES_PASSWORD}",
31 | host="${POSTGRES_HOST}",
32 | port="${POSTGRES_PORT}",
33 | )
34 | break
35 | except psycopg.OperationalError as error:
36 | sys.stderr.write("Waiting for PostgreSQL to become available...\n")
37 |
38 | if time.time() - start > suggest_unrecoverable_after:
39 | sys.stderr.write(" This is taking longer than expected. The following exception may be indicative of an unrecoverable error: '{}'\n".format(error))
40 |
41 | time.sleep(1)
42 | END
43 |
44 | >&2 echo 'PostgreSQL is available'
45 |
46 | exec "$@"
47 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/users/tests/factories.py:
--------------------------------------------------------------------------------
1 | from collections.abc import Sequence
2 | from typing import Any
3 |
4 | from django.contrib.auth import get_user_model
5 | from factory import Faker, post_generation
6 | from factory.django import DjangoModelFactory
7 |
8 |
9 | class UserFactory(DjangoModelFactory):
10 | email = Faker("email")
11 | name = Faker("name")
12 |
13 | @post_generation
14 | def password(self, create: bool, extracted: Sequence[Any], **kwargs):
15 | password = (
16 | extracted
17 | if extracted
18 | else Faker(
19 | "password",
20 | length=42,
21 | special_chars=True,
22 | digits=True,
23 | upper_case=True,
24 | lower_case=True,
25 | ).evaluate(None, None, extra={"locale": None})
26 | )
27 | self.set_password(password)
28 |
29 | @classmethod
30 | def _after_postgeneration(cls, instance, create, results=None):
31 | """Save again the instance if creating and at least one hook ran."""
32 | if create and results and not cls._meta.skip_postgeneration_save:
33 | # Some post-generation hooks ran, and may have modified us.
34 | instance.save()
35 |
36 | class Meta:
37 | model = get_user_model()
38 | django_get_or_create = ["email"]
39 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/users/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from django.contrib.auth.mixins import LoginRequiredMixin
3 | from django.contrib.messages.views import SuccessMessageMixin
4 | from django.urls import reverse
5 | from django.utils.translation import gettext_lazy as _
6 | from django.views.generic import DetailView, RedirectView, UpdateView
7 |
8 | User = get_user_model()
9 |
10 |
11 | class UserDetailView(LoginRequiredMixin, DetailView):
12 | model = User
13 | slug_field = "id"
14 | slug_url_kwarg = "id"
15 |
16 |
17 | user_detail_view = UserDetailView.as_view()
18 |
19 |
20 | class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
21 | model = User
22 | fields = ["name"]
23 | success_message = _("Information successfully updated")
24 |
25 | def get_success_url(self):
26 | assert self.request.user.is_authenticated # for mypy to know that the user is authenticated
27 | return self.request.user.get_absolute_url()
28 |
29 | def get_object(self):
30 | return self.request.user
31 |
32 |
33 | user_update_view = UserUpdateView.as_view()
34 |
35 |
36 | class UserRedirectView(LoginRequiredMixin, RedirectView):
37 | permanent = False
38 |
39 | def get_redirect_url(self):
40 | return reverse("users:detail", kwargs={"pk": self.request.user.pk})
41 |
42 |
43 | user_redirect_view = UserRedirectView.as_view()
44 |
--------------------------------------------------------------------------------
/Phase_3/docs/howto.rst:
--------------------------------------------------------------------------------
1 | How To - Project Documentation
2 | ======================================================================
3 |
4 | Get Started
5 | ----------------------------------------------------------------------
6 |
7 | Documentation can be written as rst files in `resume_builder/docs`.
8 |
9 |
10 | To build and serve docs, use the commands::
11 |
12 | docker compose -f local.yml up docs
13 |
14 |
15 |
16 | Changes to files in `docs/_source` will be picked up and reloaded automatically.
17 |
18 | `Sphinx `_ is the tool used to build documentation.
19 |
20 | Docstrings to Documentation
21 | ----------------------------------------------------------------------
22 |
23 | The sphinx extension `apidoc `_ is used to automatically document code using signatures and docstrings.
24 |
25 | Numpy or Google style docstrings will be picked up from project files and available for documentation. See the `Napoleon `_ extension for details.
26 |
27 | For an in-use example, see the `page source <_sources/users.rst.txt>`_ for :ref:`users`.
28 |
29 | To compile all docstrings automatically into documentation source files, use the command:
30 | ::
31 |
32 | make apidocs
33 |
34 |
35 | This can be done in the docker container:
36 | ::
37 |
38 | docker run --rm docs make apidocs
39 |
--------------------------------------------------------------------------------
/Phase_3/resume-generator-gh-pages/README.md:
--------------------------------------------------------------------------------
1 | # Resume generator
2 | Web based editor to create Resume in a customizable template
3 |
4 | Try it : https://nitish6174.github.io/resume-generator/
5 |
6 | **Note** : Click the "VIEW INSTRUCTIONS" button in the editor to read usage instructions.
7 |
8 | #### Features
9 | - Resume content can be edited just like a normal document editor (cut,copy,undo etc).
10 | - Entire sections can be added, reordered, removed just by cut,copy,pasting method.
11 | - Section visibility can be toggled while retaining the content.
12 | - Options provided in the left panel to modify the template and formatting.
13 | - Sub-points can be added with various bullet styles and adjustable indentation.
14 | - Script provided to merge multiple pages and compress the PDF.
15 |
16 | #### Using the merge & compress script
17 | - You must be able to run python file on your system for this.
18 | - Save the individual pages in PDF format with name ```1.pdf``` , ```2.pdf```
19 | - Download the ```compress_pdf.py``` file and open it in a text editor.
20 | - Set the following variables :
21 | - ```dir_path``` : Directory path where you saved the PDFs for individual page
22 | - ```num_of_pages``` : Number of files to merge (i.e. pages in your Resume)
23 | - ```out_file``` : Name of output file
24 | - Run this python file.
25 | - Note: As this creates a new PDF file, you may have to see permission settings or run with sudo on terminal.
26 |
27 | **Note** : Use Google Chrome
--------------------------------------------------------------------------------
/Phase_3/local.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | volumes:
4 | resume_builder_local_postgres_data: {}
5 | resume_builder_local_postgres_data_backups: {}
6 |
7 | services:
8 | django:
9 | build:
10 | context: .
11 | dockerfile: ./compose/local/django/Dockerfile
12 | image: resume_builder_local_django
13 | container_name: resume_builder_local_django
14 | depends_on:
15 | - postgres
16 | volumes:
17 | - .:/app:z
18 | env_file:
19 | - ./.envs/.local/.django
20 | - ./.envs/.local/.postgres
21 | ports:
22 | - '8000:8000'
23 | command: /start
24 |
25 | postgres:
26 | build:
27 | context: .
28 | dockerfile: ./compose/production/postgres/Dockerfile
29 | image: resume_builder_production_postgres
30 | container_name: resume_builder_local_postgres
31 | volumes:
32 | - resume_builder_local_postgres_data:/var/lib/postgresql/data
33 | - resume_builder_local_postgres_data_backups:/backups
34 | env_file:
35 | - ./.envs/.local/.postgres
36 |
37 | docs:
38 | image: resume_builder_local_docs
39 | container_name: resume_builder_local_docs
40 | build:
41 | context: .
42 | dockerfile: ./compose/local/docs/Dockerfile
43 | env_file:
44 | - ./.envs/.local/.django
45 | volumes:
46 | - ./docs:/docs:z
47 | - ./config:/app/config:z
48 | - ./resume_builder:/app/resume_builder:z
49 | ports:
50 | - '9000:9000'
51 | command: /start-docs
52 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/contrib/sites/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | import django.contrib.sites.models
2 | from django.contrib.sites.models import _simple_domain_name_validator
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = []
9 |
10 | operations = [
11 | migrations.CreateModel(
12 | name="Site",
13 | fields=[
14 | (
15 | "id",
16 | models.AutoField(
17 | verbose_name="ID",
18 | serialize=False,
19 | auto_created=True,
20 | primary_key=True,
21 | ),
22 | ),
23 | (
24 | "domain",
25 | models.CharField(
26 | max_length=100,
27 | verbose_name="domain name",
28 | validators=[_simple_domain_name_validator],
29 | ),
30 | ),
31 | ("name", models.CharField(max_length=50, verbose_name="display name")),
32 | ],
33 | options={
34 | "ordering": ("domain",),
35 | "db_table": "django_site",
36 | "verbose_name": "site",
37 | "verbose_name_plural": "sites",
38 | },
39 | bases=(models.Model,),
40 | managers=[("objects", django.contrib.sites.models.SiteManager())],
41 | )
42 | ]
43 |
--------------------------------------------------------------------------------
/Phase_3/config/settings/test.py:
--------------------------------------------------------------------------------
1 | """
2 | With these settings, tests run faster.
3 | """
4 |
5 | from .base import * # noqa
6 | from .base import env
7 |
8 | # GENERAL
9 | # ------------------------------------------------------------------------------
10 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
11 | SECRET_KEY = env(
12 | "DJANGO_SECRET_KEY",
13 | default="RNY7RYVIaw2xCqbsELoHRGM7g4t7Tz6JLUojgO0h9rM7Pa0IitHZpsyCaVYNDJEs",
14 | )
15 | # https://docs.djangoproject.com/en/dev/ref/settings/#test-runner
16 | TEST_RUNNER = "django.test.runner.DiscoverRunner"
17 |
18 | # PASSWORDS
19 | # ------------------------------------------------------------------------------
20 | # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
21 | PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
22 |
23 | # EMAIL
24 | # ------------------------------------------------------------------------------
25 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
26 | EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
27 |
28 | # DEBUGGING FOR TEMPLATES
29 | # ------------------------------------------------------------------------------
30 | TEMPLATES[0]["OPTIONS"]["debug"] = True # type: ignore # noqa: F405
31 |
32 | # MEDIA
33 | # ------------------------------------------------------------------------------
34 | # https://docs.djangoproject.com/en/dev/ref/settings/#media-url
35 | MEDIA_URL = "http://media.testserver"
36 | # Your stuff...
37 | # ------------------------------------------------------------------------------
38 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/users/managers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.hashers import make_password
2 | from django.contrib.auth.models import UserManager as DjangoUserManager
3 |
4 |
5 | class UserManager(DjangoUserManager):
6 | """Custom manager for the User model."""
7 |
8 | def _create_user(self, email: str, password: str | None, **extra_fields):
9 | """
10 | Create and save a user with the given email and password.
11 | """
12 | if not email:
13 | raise ValueError("The given email must be set")
14 | email = self.normalize_email(email)
15 | user = self.model(email=email, **extra_fields)
16 | user.password = make_password(password)
17 | user.save(using=self._db)
18 | return user
19 |
20 | def create_user(self, email: str, password: str | None = None, **extra_fields):
21 | extra_fields.setdefault("is_staff", False)
22 | extra_fields.setdefault("is_superuser", False)
23 | return self._create_user(email, password, **extra_fields)
24 |
25 | def create_superuser(self, email: str, password: str | None = None, **extra_fields):
26 | extra_fields.setdefault("is_staff", True)
27 | extra_fields.setdefault("is_superuser", True)
28 |
29 | if extra_fields.get("is_staff") is not True:
30 | raise ValueError("Superuser must have is_staff=True.")
31 | if extra_fields.get("is_superuser") is not True:
32 | raise ValueError("Superuser must have is_superuser=True.")
33 |
34 | return self._create_user(email, password, **extra_fields)
35 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/users/forms.py:
--------------------------------------------------------------------------------
1 | from allauth.account.forms import SignupForm
2 | from allauth.socialaccount.forms import SignupForm as SocialSignupForm
3 | from django.contrib.auth import forms as admin_forms
4 | from django.contrib.auth import get_user_model
5 | from django.forms import EmailField
6 | from django.utils.translation import gettext_lazy as _
7 |
8 | User = get_user_model()
9 |
10 |
11 | class UserAdminChangeForm(admin_forms.UserChangeForm):
12 | class Meta(admin_forms.UserChangeForm.Meta):
13 | model = User
14 | field_classes = {"email": EmailField}
15 |
16 |
17 | class UserAdminCreationForm(admin_forms.UserCreationForm):
18 | """
19 | Form for User Creation in the Admin Area.
20 | To change user signup, see UserSignupForm and UserSocialSignupForm.
21 | """
22 |
23 | class Meta(admin_forms.UserCreationForm.Meta):
24 | model = User
25 | fields = ("email",)
26 | field_classes = {"email": EmailField}
27 | error_messages = {
28 | "email": {"unique": _("This email has already been taken.")},
29 | }
30 |
31 |
32 | class UserSignupForm(SignupForm):
33 | """
34 | Form that will be rendered on a user sign up section/screen.
35 | Default fields will be added automatically.
36 | Check UserSocialSignupForm for accounts created from social.
37 | """
38 |
39 |
40 | class UserSocialSignupForm(SocialSignupForm):
41 | """
42 | Renders the form when user has signed up using social accounts.
43 | Default fields will be added automatically.
44 | See UserSignupForm otherwise.
45 | """
46 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/users/adapters.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import typing
4 |
5 | from allauth.account.adapter import DefaultAccountAdapter
6 | from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
7 | from django.conf import settings
8 | from django.http import HttpRequest
9 |
10 | if typing.TYPE_CHECKING:
11 | from allauth.socialaccount.models import SocialLogin
12 |
13 | from resume_builder.users.models import User
14 |
15 |
16 | class AccountAdapter(DefaultAccountAdapter):
17 | def is_open_for_signup(self, request: HttpRequest) -> bool:
18 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
19 |
20 |
21 | class SocialAccountAdapter(DefaultSocialAccountAdapter):
22 | def is_open_for_signup(self, request: HttpRequest, sociallogin: SocialLogin) -> bool:
23 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
24 |
25 | def populate_user(self, request: HttpRequest, sociallogin: SocialLogin, data: dict[str, typing.Any]) -> User:
26 | """
27 | Populates user information from social provider info.
28 |
29 | See: https://docs.allauth.org/en/latest/socialaccount/advanced.html#creating-and-populating-user-instances
30 | """
31 | user = super().populate_user(request, sociallogin, data)
32 | if not user.name:
33 | if name := data.get("name"):
34 | user.name = name
35 | elif first_name := data.get("first_name"):
36 | user.name = first_name
37 | if last_name := data.get("last_name"):
38 | user.name += f" {last_name}"
39 | return user
40 |
--------------------------------------------------------------------------------
/Phase_3/locale/README.md:
--------------------------------------------------------------------------------
1 | # Translations
2 |
3 | Start by configuring the `LANGUAGES` settings in `base.py`, by uncommenting languages you are willing to support. Then, translations strings will be placed in this folder when running:
4 |
5 | ```bash
6 | docker compose -f local.yml run --rm django python manage.py makemessages -all --no-location
7 | ```
8 |
9 | This should generate `django.po` (stands for Portable Object) files under each locale `/LC_MESSAGES/django.po`. Each translatable string in the codebase is collected with its `msgid` and need to be translated as `msgstr`, for example:
10 |
11 | ```po
12 | msgid "users"
13 | msgstr "utilisateurs"
14 | ```
15 |
16 | Once all translations are done, they need to be compiled into `.mo` files (stands for Machine Object), which are the actual binary files used by the application:
17 |
18 | ```bash
19 | docker compose -f local.yml run --rm django python manage.py compilemessages
20 | ```
21 |
22 | Note that the `.po` files are NOT used by the application directly, so if the `.mo` files are out of dates, the content won't appear as translated even if the `.po` files are up-to-date.
23 |
24 | ## Production
25 |
26 | The production image runs `compilemessages` automatically at build time, so as long as your translated source files (PO) are up-to-date, you're good to go.
27 |
28 | ## Add a new language
29 |
30 | 1. Update the [`LANGUAGES` setting](https://docs.djangoproject.com/en/stable/ref/settings/#std-setting-LANGUAGES) to your project's base settings.
31 | 2. Create the locale folder for the language next to this file, e.g. `fr_FR` for French. Make sure the case is correct.
32 | 3. Run `makemessages` (as instructed above) to generate the PO files for the new language.
33 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/users/admin.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.contrib import admin
3 | from django.contrib.auth import admin as auth_admin
4 | from django.contrib.auth import decorators, get_user_model
5 | from django.utils.translation import gettext_lazy as _
6 |
7 | from resume_builder.users.forms import UserAdminChangeForm, UserAdminCreationForm
8 |
9 | User = get_user_model()
10 |
11 | if settings.DJANGO_ADMIN_FORCE_ALLAUTH:
12 | # Force the `admin` sign in process to go through the `django-allauth` workflow:
13 | # https://docs.allauth.org/en/latest/common/admin.html#admin
14 | admin.site.login = decorators.login_required(admin.site.login) # type: ignore[method-assign]
15 |
16 |
17 | @admin.register(User)
18 | class UserAdmin(auth_admin.UserAdmin):
19 | form = UserAdminChangeForm
20 | add_form = UserAdminCreationForm
21 | fieldsets = (
22 | (None, {"fields": ("email", "password")}),
23 | (_("Personal info"), {"fields": ("name",)}),
24 | (
25 | _("Permissions"),
26 | {
27 | "fields": (
28 | "is_active",
29 | "is_staff",
30 | "is_superuser",
31 | "groups",
32 | "user_permissions",
33 | ),
34 | },
35 | ),
36 | (_("Important dates"), {"fields": ("last_login", "date_joined")}),
37 | )
38 | list_display = ["email", "name", "is_superuser"]
39 | search_fields = ["name"]
40 | ordering = ["id"]
41 | add_fieldsets = (
42 | (
43 | None,
44 | {
45 | "classes": ("wide",),
46 | "fields": ("email", "password1", "password2"),
47 | },
48 | ),
49 | )
50 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ user.name }}'s Resume
9 |
10 |
11 |
{{ user.name }}'s Resume
12 |
13 |
Education
14 | {% for education in user.education_set.all %}
15 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Phase_3/config/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for Resume Builder project.
3 |
4 | This module contains the WSGI application used by Django's development server
5 | and any production WSGI deployments. It should expose a module-level variable
6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
7 | this application via the ``WSGI_APPLICATION`` setting.
8 |
9 | Usually you will have the standard Django WSGI application here, but it also
10 | might make sense to replace the whole Django WSGI application with a custom one
11 | that later delegates to the Django one. For example, you could introduce WSGI
12 | middleware here, or combine a Django application with an application of another
13 | framework.
14 |
15 | """
16 |
17 | import os
18 | import sys
19 | from pathlib import Path
20 |
21 | from django.core.wsgi import get_wsgi_application
22 |
23 | # This allows easy placement of apps within the interior
24 | # resume_builder directory.
25 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
26 | sys.path.append(str(BASE_DIR / "resume_builder"))
27 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
28 | # if running multiple sites in the same mod_wsgi process. To fix this, use
29 | # mod_wsgi daemon mode with each site in its own daemon process, or use
30 | # os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production"
31 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")
32 |
33 | # This application object is used by any WSGI server configured to use this
34 | # file. This includes Django's development server, if the WSGI_APPLICATION
35 | # setting points here.
36 | application = get_wsgi_application()
37 | # Apply WSGI middleware here.
38 | # from helloworld.wsgi import HelloWorldApplication
39 | # application = HelloWorldApplication(application)
40 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/users/tests/test_managers.py:
--------------------------------------------------------------------------------
1 | from io import StringIO
2 |
3 | import pytest
4 | from django.core.management import call_command
5 |
6 | from resume_builder.users.models import User
7 |
8 |
9 | @pytest.mark.django_db
10 | class TestUserManager:
11 | def test_create_user(self):
12 | user = User.objects.create_user(
13 | email="john@example.com",
14 | password="something-r@nd0m!",
15 | )
16 | assert user.email == "john@example.com"
17 | assert not user.is_staff
18 | assert not user.is_superuser
19 | assert user.check_password("something-r@nd0m!")
20 | assert user.username is None
21 |
22 | def test_create_superuser(self):
23 | user = User.objects.create_superuser(
24 | email="admin@example.com",
25 | password="something-r@nd0m!",
26 | )
27 | assert user.email == "admin@example.com"
28 | assert user.is_staff
29 | assert user.is_superuser
30 | assert user.username is None
31 |
32 | def test_create_superuser_username_ignored(self):
33 | user = User.objects.create_superuser(
34 | email="test@example.com",
35 | password="something-r@nd0m!",
36 | )
37 | assert user.username is None
38 |
39 |
40 | @pytest.mark.django_db
41 | def test_createsuperuser_command():
42 | """Ensure createsuperuser command works with our custom manager."""
43 | out = StringIO()
44 | command_result = call_command(
45 | "createsuperuser",
46 | "--email",
47 | "henry@example.com",
48 | interactive=False,
49 | stdout=out,
50 | )
51 |
52 | assert command_result is None
53 | assert out.getvalue() == "Superuser created successfully.\n"
54 | user = User.objects.get(email="henry@example.com")
55 | assert not user.has_usable_password()
56 |
--------------------------------------------------------------------------------
/Phase_3/compose/production/postgres/maintenance/restore:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 |
4 | ### Restore database from a backup.
5 | ###
6 | ### Parameters:
7 | ### <1> filename of an existing backup.
8 | ###
9 | ### Usage:
10 | ### $ docker compose -f .yml (exec |run --rm) postgres restore <1>
11 |
12 |
13 | set -o errexit
14 | set -o pipefail
15 | set -o nounset
16 |
17 |
18 | working_dir="$(dirname ${0})"
19 | source "${working_dir}/_sourced/constants.sh"
20 | source "${working_dir}/_sourced/messages.sh"
21 |
22 |
23 | if [[ -z ${1+x} ]]; then
24 | message_error "Backup filename is not specified yet it is a required parameter. Make sure you provide one and try again."
25 | exit 1
26 | fi
27 | backup_filename="${BACKUP_DIR_PATH}/${1}"
28 | if [[ ! -f "${backup_filename}" ]]; then
29 | message_error "No backup with the specified filename found. Check out the 'backups' maintenance script output to see if there is one and try again."
30 | exit 1
31 | fi
32 |
33 | message_welcome "Restoring the '${POSTGRES_DB}' database from the '${backup_filename}' backup..."
34 |
35 | if [[ "${POSTGRES_USER}" == "postgres" ]]; then
36 | message_error "Restoring as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again."
37 | exit 1
38 | fi
39 |
40 | export PGHOST="${POSTGRES_HOST}"
41 | export PGPORT="${POSTGRES_PORT}"
42 | export PGUSER="${POSTGRES_USER}"
43 | export PGPASSWORD="${POSTGRES_PASSWORD}"
44 | export PGDATABASE="${POSTGRES_DB}"
45 |
46 | message_info "Dropping the database..."
47 | dropdb "${PGDATABASE}"
48 |
49 | message_info "Creating a new database..."
50 | createdb --owner="${POSTGRES_USER}"
51 |
52 | message_info "Applying the backup to the new database..."
53 | gunzip -c "${backup_filename}" | psql "${POSTGRES_DB}"
54 |
55 | message_success "The '${POSTGRES_DB}' database has been restored from the '${backup_filename}' backup."
56 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/resume/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import filters, viewsets
2 |
3 | from .models import Education, Experience, Language, Project, Resume, Skill
4 | from .serializers import (
5 | EducationSerializer,
6 | ExperienceSerializer,
7 | LanguageSerializer,
8 | ProjectSerializer,
9 | ResumeSerializer,
10 | SkillSerializer,
11 | )
12 |
13 | from django.contrib.auth import get_user_model
14 |
15 | User = get_user_model()
16 |
17 | # import render
18 | from django.shortcuts import render
19 |
20 | # template view
21 | from django.views.generic import TemplateView
22 |
23 |
24 | class BaseViewSet(viewsets.ModelViewSet):
25 | filter_backends = [filters.SearchFilter, filters.OrderingFilter]
26 | search_fields = ["name"]
27 | ordering_fields = ["name"]
28 |
29 |
30 | class ResumeViewSet(BaseViewSet):
31 | queryset = Resume.objects.all()
32 | serializer_class = ResumeSerializer
33 |
34 |
35 | class EducationViewSet(BaseViewSet):
36 | queryset = Education.objects.all()
37 | serializer_class = EducationSerializer
38 |
39 |
40 | class ExperienceViewSet(BaseViewSet):
41 | queryset = Experience.objects.all()
42 | serializer_class = ExperienceSerializer
43 |
44 |
45 | class SkillViewSet(BaseViewSet):
46 | queryset = Skill.objects.all()
47 | serializer_class = SkillSerializer
48 |
49 |
50 | class ProjectViewSet(BaseViewSet):
51 | queryset = Project.objects.all()
52 | serializer_class = ProjectSerializer
53 |
54 |
55 | class LanguageViewSet(BaseViewSet):
56 | queryset = Language.objects.all()
57 | serializer_class = LanguageSerializer
58 |
59 |
60 | from .forms import ResumeForm
61 |
62 | class ResumeTemplate(TemplateView):
63 | template_name = "resume.html"
64 |
65 | def get_context_data(self, **kwargs):
66 | context = super().get_context_data(**kwargs)
67 | context["form"] = ResumeForm()
68 | return context
--------------------------------------------------------------------------------
/Phase_3/requirements/local.txt:
--------------------------------------------------------------------------------
1 | -r base.txt
2 |
3 | Werkzeug[watchdog]==3.0.1 # https://github.com/pallets/werkzeug
4 | ipdb==0.13.13 # https://github.com/gotcha/ipdb
5 | psycopg[c]==3.1.17 # https://github.com/psycopg/psycopg
6 |
7 | # Testing
8 | # ------------------------------------------------------------------------------
9 | mypy==1.7.1 # https://github.com/python/mypy
10 | django-stubs[compatible-mypy]==4.2.7 # https://github.com/typeddjango/django-stubs
11 | pytest==7.4.4 # https://github.com/pytest-dev/pytest
12 | pytest-sugar==0.9.7 # https://github.com/Frozenball/pytest-sugar
13 | djangorestframework-stubs[compatible-mypy]==3.14.5 # https://github.com/typeddjango/djangorestframework-stubs
14 |
15 | # Documentation
16 | # ------------------------------------------------------------------------------
17 | sphinx==7.2.6 # https://github.com/sphinx-doc/sphinx
18 | sphinx-autobuild==2021.3.14 # https://github.com/GaretJax/sphinx-autobuild
19 |
20 | # Code quality
21 | # ------------------------------------------------------------------------------
22 | flake8==7.0.0 # https://github.com/PyCQA/flake8
23 | flake8-isort==6.1.1 # https://github.com/gforcada/flake8-isort
24 | coverage==7.4.1 # https://github.com/nedbat/coveragepy
25 | black==24.1.0 # https://github.com/psf/black
26 | djlint==1.34.1 # https://github.com/Riverside-Healthcare/djLint
27 | pylint-django==2.5.5 # https://github.com/PyCQA/pylint-django
28 | pre-commit==3.6.0 # https://github.com/pre-commit/pre-commit
29 |
30 | # Django
31 | # ------------------------------------------------------------------------------
32 | factory-boy==3.3.0 # https://github.com/FactoryBoy/factory_boy
33 |
34 | django-debug-toolbar==4.2.0 # https://github.com/jazzband/django-debug-toolbar
35 | django-extensions==3.2.3 # https://github.com/django-extensions/django-extensions
36 | django-coverage-plugin==3.1.0 # https://github.com/nedbat/django_coverage_plugin
37 | pytest-django==4.7.0 # https://github.com/pytest-dev/pytest-django
38 |
--------------------------------------------------------------------------------
/Phase_3/compose/production/traefik/traefik.yml:
--------------------------------------------------------------------------------
1 | log:
2 | level: INFO
3 |
4 | entryPoints:
5 | web:
6 | # http
7 | address: ':80'
8 | http:
9 | # https://doc.traefik.io/traefik/routing/entrypoints/#entrypoint
10 | redirections:
11 | entryPoint:
12 | to: web-secure
13 |
14 | web-secure:
15 | # https
16 | address: ':443'
17 |
18 | certificatesResolvers:
19 | letsencrypt:
20 | # https://doc.traefik.io/traefik/https/acme/#lets-encrypt
21 | acme:
22 | email: 'alireza-parvaresh@example.com'
23 | storage: /etc/traefik/acme/acme.json
24 | # https://doc.traefik.io/traefik/https/acme/#httpchallenge
25 | httpChallenge:
26 | entryPoint: web
27 |
28 | http:
29 | routers:
30 | web-secure-router:
31 | rule: 'Host(`example.com`) || Host(`www.example.com`)'
32 | entryPoints:
33 | - web-secure
34 | middlewares:
35 | - csrf
36 | service: django
37 | tls:
38 | # https://doc.traefik.io/traefik/routing/routers/#certresolver
39 | certResolver: letsencrypt
40 |
41 | web-media-router:
42 | rule: '(Host(`example.com`) || Host(`www.example.com`)) && PathPrefix(`/media/`)'
43 | entryPoints:
44 | - web-secure
45 | middlewares:
46 | - csrf
47 | service: django-media
48 | tls:
49 | certResolver: letsencrypt
50 |
51 | middlewares:
52 | csrf:
53 | # https://doc.traefik.io/traefik/master/middlewares/http/headers/#hostsproxyheaders
54 | # https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
55 | headers:
56 | hostsProxyHeaders: ['X-CSRFToken']
57 |
58 | services:
59 | django:
60 | loadBalancer:
61 | servers:
62 | - url: http://django:5000
63 |
64 | django-media:
65 | loadBalancer:
66 | servers:
67 | - url: http://nginx:80
68 |
69 | providers:
70 | # https://doc.traefik.io/traefik/master/providers/file/
71 | file:
72 | filename: /etc/traefik/traefik.yml
73 | watch: true
74 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/templates/account/login.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account socialaccount %}
5 | {% load crispy_forms_tags %}
6 |
7 | {% block head_title %}
8 | {% translate "Sign In" %}
9 | {% endblock head_title %}
10 | {% block inner %}
11 |
{% translate "Sign In" %}
12 | {% get_providers as socialaccount_providers %}
13 | {% if socialaccount_providers %}
14 |
15 | {% translate "Please sign in with one of your existing third party accounts:" %}
16 | {% if ACCOUNT_ALLOW_REGISTRATION %}
17 | {% blocktranslate trimmed %}
18 | Or, sign up
19 | for a {{ site_name }} account and sign in below:
20 | {% endblocktranslate %}
21 | {% endif %}
22 |
23 |
24 |
25 | {% include "socialaccount/snippets/provider_list.html" with process="login" %}
26 |
27 |
{% translate "or" %}
28 |
29 | {% include "socialaccount/snippets/login_extra.html" %}
30 | {% else %}
31 | {% if ACCOUNT_ALLOW_REGISTRATION %}
32 |
33 | {% blocktranslate trimmed %}
34 | If you have not created an account yet, then please
35 | sign up first.
36 | {% endblocktranslate %}
37 |
{% translate "The following e-mail addresses are associated with your account:" %}
14 |
51 | {% else %}
52 |
53 | {% translate "Warning:" %} {% translate "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}
54 |
55 | {% endif %}
56 |
{% translate "Add E-mail Address" %}
57 |
62 | {% endblock inner %}
63 | {% block inline_javascript %}
64 | {{ block.super }}
65 |
79 | {% endblock inline_javascript %}
80 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/users/tests/test_views.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from django.conf import settings
3 | from django.contrib import messages
4 | from django.contrib.auth.models import AnonymousUser
5 | from django.contrib.messages.middleware import MessageMiddleware
6 | from django.contrib.sessions.middleware import SessionMiddleware
7 | from django.http import HttpRequest, HttpResponseRedirect
8 | from django.test import RequestFactory
9 | from django.urls import reverse
10 | from django.utils.translation import gettext_lazy as _
11 |
12 | from resume_builder.users.forms import UserAdminChangeForm
13 | from resume_builder.users.models import User
14 | from resume_builder.users.tests.factories import UserFactory
15 | from resume_builder.users.views import UserRedirectView, UserUpdateView, user_detail_view
16 |
17 | pytestmark = pytest.mark.django_db
18 |
19 |
20 | class TestUserUpdateView:
21 | """
22 | TODO:
23 | extracting view initialization code as class-scoped fixture
24 | would be great if only pytest-django supported non-function-scoped
25 | fixture db access -- this is a work-in-progress for now:
26 | https://github.com/pytest-dev/pytest-django/pull/258
27 | """
28 |
29 | def dummy_get_response(self, request: HttpRequest):
30 | return None
31 |
32 | def test_get_success_url(self, user: User, rf: RequestFactory):
33 | view = UserUpdateView()
34 | request = rf.get("/fake-url/")
35 | request.user = user
36 |
37 | view.request = request
38 | assert view.get_success_url() == f"/users/{user.pk}/"
39 |
40 | def test_get_object(self, user: User, rf: RequestFactory):
41 | view = UserUpdateView()
42 | request = rf.get("/fake-url/")
43 | request.user = user
44 |
45 | view.request = request
46 |
47 | assert view.get_object() == user
48 |
49 | def test_form_valid(self, user: User, rf: RequestFactory):
50 | view = UserUpdateView()
51 | request = rf.get("/fake-url/")
52 |
53 | # Add the session/message middleware to the request
54 | SessionMiddleware(self.dummy_get_response).process_request(request)
55 | MessageMiddleware(self.dummy_get_response).process_request(request)
56 | request.user = user
57 |
58 | view.request = request
59 |
60 | # Initialize the form
61 | form = UserAdminChangeForm()
62 | form.cleaned_data = {}
63 | form.instance = user
64 | view.form_valid(form)
65 |
66 | messages_sent = [m.message for m in messages.get_messages(request)]
67 | assert messages_sent == [_("Information successfully updated")]
68 |
69 |
70 | class TestUserRedirectView:
71 | def test_get_redirect_url(self, user: User, rf: RequestFactory):
72 | view = UserRedirectView()
73 | request = rf.get("/fake-url")
74 | request.user = user
75 |
76 | view.request = request
77 | assert view.get_redirect_url() == f"/users/{user.pk}/"
78 |
79 |
80 | class TestUserDetailView:
81 | def test_authenticated(self, user: User, rf: RequestFactory):
82 | request = rf.get("/fake-url/")
83 | request.user = UserFactory()
84 | response = user_detail_view(request, pk=user.pk)
85 |
86 | assert response.status_code == 200
87 |
88 | def test_not_authenticated(self, user: User, rf: RequestFactory):
89 | request = rf.get("/fake-url/")
90 | request.user = AnonymousUser()
91 | response = user_detail_view(request, pk=user.pk)
92 | login_url = reverse(settings.LOGIN_URL)
93 |
94 | assert isinstance(response, HttpResponseRedirect)
95 | assert response.status_code == 302
96 | assert response.url == f"{login_url}?next=/fake-url/"
97 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/users/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | import django.contrib.auth.models
2 | import django.contrib.auth.validators
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 | import resume_builder.users.models
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | initial = True
12 |
13 | dependencies = [
14 | ("auth", "0012_alter_user_first_name_max_length"),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name="User",
20 | fields=[
21 | (
22 | "id",
23 | models.BigAutoField(
24 | auto_created=True,
25 | primary_key=True,
26 | serialize=False,
27 | verbose_name="ID",
28 | ),
29 | ),
30 | ("password", models.CharField(max_length=128, verbose_name="password")),
31 | (
32 | "last_login",
33 | models.DateTimeField(blank=True, null=True, verbose_name="last login"),
34 | ),
35 | (
36 | "is_superuser",
37 | models.BooleanField(
38 | default=False,
39 | help_text="Designates that this user has all permissions without explicitly assigning them.",
40 | verbose_name="superuser status",
41 | ),
42 | ),
43 | (
44 | "email",
45 | models.EmailField(unique=True, max_length=254, verbose_name="email address"),
46 | ),
47 | (
48 | "is_staff",
49 | models.BooleanField(
50 | default=False,
51 | help_text="Designates whether the user can log into this admin site.",
52 | verbose_name="staff status",
53 | ),
54 | ),
55 | (
56 | "is_active",
57 | models.BooleanField(
58 | default=True,
59 | help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
60 | verbose_name="active",
61 | ),
62 | ),
63 | (
64 | "date_joined",
65 | models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined"),
66 | ),
67 | (
68 | "name",
69 | models.CharField(blank=True, max_length=255, verbose_name="Name of User"),
70 | ),
71 | (
72 | "groups",
73 | models.ManyToManyField(
74 | blank=True,
75 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
76 | related_name="user_set",
77 | related_query_name="user",
78 | to="auth.Group",
79 | verbose_name="groups",
80 | ),
81 | ),
82 | (
83 | "user_permissions",
84 | models.ManyToManyField(
85 | blank=True,
86 | help_text="Specific permissions for this user.",
87 | related_name="user_set",
88 | related_query_name="user",
89 | to="auth.Permission",
90 | verbose_name="user permissions",
91 | ),
92 | ),
93 | ],
94 | options={
95 | "verbose_name": "user",
96 | "verbose_name_plural": "users",
97 | "abstract": False,
98 | },
99 | managers=[
100 | ("objects", resume_builder.users.models.UserManager()),
101 | ],
102 | ),
103 | ]
104 |
--------------------------------------------------------------------------------
/Phase_2/Phase_2.tex:
--------------------------------------------------------------------------------
1 | \documentclass[]{article}
2 | \usepackage{color}
3 | \usepackage{graphicx}
4 | \usepackage{fancyhdr}
5 | \usepackage{geometry}
6 | \usepackage{verbatim}
7 | \usepackage[pagebackref=true,colorlinks,linkcolor=blue,citecolor=green]{hyperref}
8 | \usepackage{ptext}
9 | \usepackage{atbegshi}
10 | \usepackage{xepersian}
11 | \settextfont{Zar}
12 | \settextfont{XB Niloofar}
13 | \setlatintextfont{Junicode}
14 |
15 | \usepackage[width=0.00cm, height=0.00cm, left=2cm, right=1.5cm, top=2.50cm, bottom=2.5cm]{}
16 |
17 | \title{
18 | \begin{center}
19 | \includegraphics[width=8cm,height=10cm]{AUT.png}
20 | \end{center}
21 |
22 | \LARGE
23 | درس: اصول طراحی نرم افزار \\
24 | \Large
25 | استاد: دکتر احسان علیرضایی \\
26 | \Large
27 | فاز یک پروژه
28 |
29 | }
30 | \author{
31 | گروه 4: \\
32 | مهدی مهدوی - 9833067 \\
33 | علیرضا پرورش - 9912013 \\
34 | آروین اسدی - 9913701 \\
35 | امیرمحمد کمانی - 9913704
36 | }
37 | \fancypagestyle{logo}{
38 | \fancyhf{}
39 | \fancyhfoffset[R]{3.4cm}\fancyfoot[CE,CO]{~\\[.5cm]\thepage}
40 | \fancyhfoffset[L]{3.4cm}
41 | \fancyhead[RE,RO]{~\\[-2cm]\includegraphics[height=2.3cm]{MCS}}
42 | \renewcommand{\headrulewidth}{0pt}
43 |
44 | }
45 | \pagestyle{logo}
46 |
47 | \textheight=21.5cm
48 |
49 | \begin{document}
50 | \maketitle
51 | \thispagestyle{empty}
52 | \newpage
53 | \tableofcontents
54 | \newpage
55 |
56 | \fontsize{12pt}{14pt}
57 | \LARGE
58 | % \selectfont
59 |
60 | \section{\huge{\lr{Diagrams in Enterprise Architect}}}
61 | دیاگرامهای خواسته شده در این نرم افزار را میتوانید در فایل
62 |
63 | \lr{P1\_9833067\_9912013\_9913701\_9913704.qea}
64 | مشاهده کنید. لازم به ذکر است فایل فوق حاوی 1
65 | \lr{Requirement Diagram}،
66 | 9 تا
67 | \lr{Use Case}
68 | (\lr{use case}
69 | سناریوهای 2 و 5 یکسان است و نام گذاری سایر
70 | \lr{use case}ها
71 | متناسب با شمارۀ سناریوهاست.)، 9 تا
72 | \lr{Activity Diagram}،
73 | 2 تا
74 | \lr{Sequence Diagram} و
75 | 1 \lr{Class Diagram}
76 | میباشد.
77 |
78 | \section{\huge{تحلیل ریشهای}}
79 | برای تحلیل علل ریشهای مسئله طراحی اپلیکیشن رزومه ساز، میتوان از روشهای مختلف تحلیل علت ریشهای استفاده کرد.
80 | ابتدا، یکی از روشهای معروف آن یعنی "۵ چرا" را بررسی میکنیم:
81 | روش ۵ چرا یا همان
82 | \lr{The 5 whys}
83 |
84 | مشکل:
85 |
86 | کیفیت نامناسب رابط کاربری و تجربه کاربری در اپلیکیشن.
87 |
88 | چرا؟ (شماره ۱)
89 |
90 | چرا رابط کاربری و تجربه کاربری ضعیف است؟
91 |
92 | دلیل: زیرا نیازهای واقعی کاربران در نظر گرفته نشده است.
93 |
94 | چرا؟ (شماره ۲)
95 |
96 | چرا نیازهای واقعی کاربران در نظر گرفته نشده است؟
97 |
98 | دلیل: چون که نقدها و بازخوردهای کاربران به درستی مورد بررسی قرار نگرفته است.
99 |
100 | چرا؟ (شماره ۳)
101 |
102 | چرا نقدها و بازخوردهای کاربران به درستی مورد بررسی قرار نگرفته است؟
103 |
104 | دلیل: چون فرآیند جامعی برای ارزیابی و بازخورد کاربران وجود ندارد!
105 |
106 | چرا؟ (شماره ۴)
107 |
108 | چرا فرآیند جامعی برای ارزیابی و بازخورد کاربران وجود ندارد؟
109 |
110 | دلیل: به علت عدم وجود یک سیستم سازمانی یا فرآیند مشخص برای جمع آوری و ارزیابی بازخوردهای کاربران.
111 |
112 | چرا؟ (شماره ۵)
113 |
114 | چرا یک سیستم سازمانی یا فرآیند مشخصی برای جمع آوری و ارزیابی بازخوردهای کاربران وجود ندارد؟
115 |
116 | دلیل: به علت عدم تخصیص منابع یا عدم توجه کافی به نیازمندیهای کاربران در فرآیند توسعه و طراحی.
117 |
118 | همان گونه که در ابتدا اشاره شد، روشهای دیگری برای تحلیل علل ریشهای مسئلۀ طراحی اپلیکیشن رزومه ساز نیز وجود دارد که در ادامه به برخی دیگر از آن ها اشاره خواهیم کرد:
119 |
120 | نمودار پارتو (\lr{pareto}):
121 |
122 | با استفاده از نمودار پارتو میتوان عواملی که بیشترین تأثیر ممکن را بر روی کیفیت رابط کاربری و تجربه کاربری دارند، مشخص کرد.
123 |
124 | به طور مثال ممکن است که نقدها و بازخوردهای کاربران 80 درصد مشکلات اصلی را تشکیل دهد.
125 |
126 | نمودار استخوان ماهی (\lr{fish-bone}):
127 |
128 | این نمودار به ما کمک میکند عوامل مختلفی که ممکن است در نادرست بودن تشخیص و فهم رابط کاربری و تجربه کاربری دخیل باشند را مشخص کنیم.
129 |
130 | این عوامل ممکن است مربوط به دستگاهها (مثل موبایل و تبلت)، محیط (شامل نور و رنگها)، متون و ترتیب اطلاعات (اینکه به طور منظم سامان دهی شدند یا نه) و... باشد.
131 |
132 | نمودار پراکندگی (\lr{scatter plot diagram}):
133 |
134 | با استفاده از این نمودار میتوان رابطۀ بین بازخوردهای کاربران با کیفیت رابط کاربری و تجربه کاربری را بررسی کرد.
135 |
136 | در این نمودار به سوالاتی مانند سوال زیر به دقت پاسخ داده میشود:
137 |
138 | آیا افرادی که نقدها را میدهند، مشکلات مشابهی را تجربه میکنند؟
139 |
140 | تحلیل موضوع عدم موفقیت و تأثیرات آن (\lr{fmea}):
141 |
142 | این روش میتواند در تعیین اولویت اقداماتی که قرار است برای بهبود رابط کاربری و تجربه کاربری انجام دهیم، کمک کند که بفهمیم کدام یک از اقدامات ضروریتر و بهتر است و بیشتر موجب بهبود میشود.
143 |
144 | به طور مثال تعیین ریسکهایی که ممکن است تجربۀ کاربری را تحت تأثیر قرار دهد و انجام اقداماتی برای پیشگیری از آن، از مسائل مربوط به روش تحلیل موضوع عدم موفقیت میباشد.
145 |
146 | در نهایت، با توجه به این مثالها، تحلیل علت ریشهای در مسائل مرتبط با طراحی اپلیکیشن رزومه ساز و شناسایی عوامل اصلی که تأثیر مستقیم بر کیفیت رابط کاربری و تجربه کاربری دارند، میتواند در ارائه راهحلهای دقیق و کارآمد مفید باشند و به بهبود و توسعه اپلیکیشن کمک کنند.
147 |
148 | \end{document}
149 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/Phase_3/resume_builder/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 | {% load static i18n %}
3 | {% get_current_language as LANGUAGE_CODE %}
4 |
5 |
6 |
7 |
8 |
9 | {% block title %}
10 | Resume Builder
11 | {% endblock title %}
12 |
13 |
14 |
16 |
18 |
19 | {% block css %}
20 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {% endblock css %}
34 |
36 | {# Placed at the top of the document so pages load faster with defer #}
37 | {% block javascript %}
38 |
39 |
40 |
41 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | {% endblock javascript %}
56 |
57 |
58 |
59 |
104 |
105 |
106 | {% if messages %}
107 | {% for message in messages %}
108 |