├── Phase_1 ├── readme.md └── phase_1.pdf ├── Phase_2 ├── readme.md ├── Diagrams │ └── Phase_2(Diagrams).qea ├── Phase_2(Root_Cause_Analysis).pdf └── Phase_2.tex ├── README.md └── Phase_3 ├── config ├── __init__.py ├── settings │ ├── __init__.py │ ├── test.py │ ├── local.py │ ├── production.py │ └── base.py ├── api_router.py ├── wsgi.py └── urls.py ├── CONTRIBUTORS.txt ├── resume_builder ├── resume │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0003_resume_languages.py │ │ ├── 0001_initial.py │ │ └── 0002_resume_user_alter_education_degree_and_more.py │ ├── tests.py │ ├── apps.py │ ├── admin.py │ ├── urls.py │ ├── forms.py │ ├── serializers.py │ ├── views.py │ └── models.py ├── users │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── serializers.py │ │ └── views.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_models.py │ │ ├── test_urls.py │ │ ├── test_drf_urls.py │ │ ├── test_swagger.py │ │ ├── test_drf_views.py │ │ ├── test_forms.py │ │ ├── factories.py │ │ ├── test_managers.py │ │ ├── test_admin.py │ │ └── test_views.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── context_processors.py │ ├── apps.py │ ├── urls.py │ ├── models.py │ ├── views.py │ ├── managers.py │ ├── forms.py │ ├── adapters.py │ └── admin.py ├── templates │ ├── pages │ │ ├── home.html │ │ └── about.html │ ├── 403.html │ ├── 404.html │ ├── 403_csrf.html │ ├── account │ │ ├── account_inactive.html │ │ ├── base.html │ │ ├── password_reset_from_key_done.html │ │ ├── signup_closed.html │ │ ├── verification_sent.html │ │ ├── password_reset_done.html │ │ ├── password_change.html │ │ ├── password_set.html │ │ ├── logout.html │ │ ├── signup.html │ │ ├── verified_email_required.html │ │ ├── password_reset.html │ │ ├── email_confirm.html │ │ ├── password_reset_from_key.html │ │ ├── login.html │ │ └── email.html │ ├── 500.html │ ├── users │ │ ├── user_form.html │ │ └── user_detail.html │ ├── index.html │ └── base.html ├── static │ ├── images │ │ └── iitg_logo.png │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── css │ │ ├── thin_scroll.css │ │ ├── column_scroll.css │ │ ├── theme.css │ │ └── main.css │ └── js │ │ └── main.js ├── __init__.py ├── contrib │ ├── __init__.py │ └── sites │ │ ├── __init__.py │ │ └── migrations │ │ ├── __init__.py │ │ ├── 0004_alter_options_ordering_domain.py │ │ ├── 0002_alter_domain_unique.py │ │ ├── 0001_initial.py │ │ └── 0003_set_site_domain_and_name.py └── conftest.py ├── docs ├── __init__.py ├── users.rst ├── index.rst ├── Makefile ├── make.bat ├── howto.rst └── conf.py ├── compose ├── local │ ├── docs │ │ ├── start │ │ └── Dockerfile │ └── django │ │ ├── start │ │ └── Dockerfile └── production │ ├── nginx │ ├── Dockerfile │ └── default.conf │ ├── postgres │ ├── maintenance │ │ ├── _sourced │ │ │ ├── constants.sh │ │ │ ├── yes_no.sh │ │ │ ├── countdown.sh │ │ │ └── messages.sh │ │ ├── backups │ │ ├── rmbackup │ │ ├── backup │ │ └── restore │ └── Dockerfile │ ├── django │ ├── start │ ├── entrypoint │ └── Dockerfile │ └── traefik │ ├── Dockerfile │ └── traefik.yml ├── locale ├── en_US │ └── LC_MESSAGES │ │ └── django.po ├── README.md ├── pt_BR │ └── LC_MESSAGES │ │ └── django.po └── fr_FR │ └── LC_MESSAGES │ └── django.po ├── requirements ├── production.txt ├── base.txt └── local.txt ├── setup.cfg ├── resume-generator-gh-pages ├── compress_pdf.py └── README.md ├── merge_production_dotenvs_in_dotenv.py ├── README.md ├── tests └── test_merge_production_dotenvs_in_dotenv.py ├── LICENSE ├── manage.py ├── local.yml └── pyproject.toml /Phase_1/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Phase_2/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # test -------------------------------------------------------------------------------- /Phase_3/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Phase_3/CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | no 2 | 3 | -------------------------------------------------------------------------------- /Phase_3/config/settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/resume/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/resume/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/resume/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/pages/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/pages/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | -------------------------------------------------------------------------------- /Phase_1/phase_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parvvaresh/resume_maker/main/Phase_1/phase_1.pdf -------------------------------------------------------------------------------- /Phase_3/docs/__init__.py: -------------------------------------------------------------------------------- 1 | # Included so that Django's startproject comment runs against the docs directory 2 | -------------------------------------------------------------------------------- /Phase_2/Diagrams/Phase_2(Diagrams).qea: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parvvaresh/resume_maker/main/Phase_2/Diagrams/Phase_2(Diagrams).qea -------------------------------------------------------------------------------- /Phase_2/Phase_2(Root_Cause_Analysis).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parvvaresh/resume_maker/main/Phase_2/Phase_2(Root_Cause_Analysis).pdf -------------------------------------------------------------------------------- /Phase_3/compose/local/docs/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | exec make livehtml 8 | -------------------------------------------------------------------------------- /Phase_3/compose/production/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/nginx:1.17.8-alpine 2 | COPY ./compose/production/nginx/default.conf /etc/nginx/conf.d/default.conf 3 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/static/images/iitg_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parvvaresh/resume_maker/main/Phase_3/resume_builder/static/images/iitg_logo.png -------------------------------------------------------------------------------- /Phase_3/compose/production/postgres/maintenance/_sourced/constants.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | BACKUP_DIR_PATH='/backups' 5 | BACKUP_FILE_PREFIX='backup' 6 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.0" 2 | __version_info__ = tuple(int(num) if num.isdigit() else num for num in __version__.replace("-", ".", 1).split(".")) 3 | -------------------------------------------------------------------------------- /Phase_3/compose/production/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | location /media/ { 5 | alias /usr/share/nginx/media/; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parvvaresh/resume_maker/main/Phase_3/resume_builder/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /Phase_3/resume_builder/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parvvaresh/resume_maker/main/Phase_3/resume_builder/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /Phase_3/resume_builder/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parvvaresh/resume_maker/main/Phase_3/resume_builder/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /Phase_3/resume_builder/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parvvaresh/resume_maker/main/Phase_3/resume_builder/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /Phase_3/compose/local/django/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | 8 | python manage.py migrate 9 | exec python manage.py runserver_plus 0.0.0.0:8000 10 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/resume/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ResumeConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "resume_builder.resume" 7 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from resume_builder.users.models import User 2 | 3 | 4 | def test_user_get_absolute_url(user: User): 5 | assert user.get_absolute_url() == f"/users/{user.pk}/" 6 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/contrib/sites/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/contrib/sites/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | -------------------------------------------------------------------------------- /Phase_3/compose/production/django/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | 8 | python /app/manage.py collectstatic --noinput 9 | 10 | exec /usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app 11 | -------------------------------------------------------------------------------- /Phase_3/compose/production/traefik/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/traefik:2.10.7 2 | RUN mkdir -p /etc/traefik/acme \ 3 | && touch /etc/traefik/acme/acme.json \ 4 | && chmod 600 /etc/traefik/acme/acme.json 5 | COPY ./compose/production/traefik/traefik.yml /etc/traefik 6 | -------------------------------------------------------------------------------- /Phase_3/compose/production/postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/postgres:15 2 | 3 | COPY ./compose/production/postgres/maintenance /usr/local/bin/maintenance 4 | RUN chmod +x /usr/local/bin/maintenance/* 5 | RUN mv /usr/local/bin/maintenance/* /usr/local/bin \ 6 | && rmdir /usr/local/bin/maintenance 7 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def allauth_settings(request): 5 | """Expose some settings from django-allauth in templates.""" 6 | return { 7 | "ACCOUNT_ALLOW_REGISTRATION": settings.ACCOUNT_ALLOW_REGISTRATION, 8 | } 9 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/resume/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Education, Experience, Language, Project, Resume, Skill 4 | 5 | admin.site.register(Resume) 6 | admin.site.register(Education) 7 | admin.site.register(Experience) 8 | admin.site.register(Skill) 9 | admin.site.register(Project) 10 | admin.site.register(Language) 11 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/403.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Forbidden (403){% endblock title %} 4 | {% block content %} 5 |

Forbidden (403)

6 |

7 | {% if exception %} 8 | {{ exception }} 9 | {% else %} 10 | You're not allowed to access this page. 11 | {% endif %} 12 |

13 | {% endblock content %} 14 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Page not found{% endblock title %} 4 | {% block content %} 5 |

Page not found

6 |

7 | {% if exception %} 8 | {{ exception }} 9 | {% else %} 10 | This is not the page you were looking for. 11 | {% endif %} 12 |

13 | {% endblock content %} 14 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/403_csrf.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Forbidden (403){% endblock title %} 4 | {% block content %} 5 |

Forbidden (403)

6 |

7 | {% if exception %} 8 | {{ exception }} 9 | {% else %} 10 | You're not allowed to access this page. 11 | {% endif %} 12 |

13 | {% endblock content %} 14 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/account_inactive.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %} 6 | {% translate "Account Inactive" %} 7 | {% endblock head_title %} 8 | {% block inner %} 9 |

{% translate "Account Inactive" %}

10 |

{% translate "This account is inactive." %}

11 | {% endblock inner %} 12 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from resume_builder.users.models import User 4 | from resume_builder.users.tests.factories import UserFactory 5 | 6 | 7 | @pytest.fixture(autouse=True) 8 | def media_storage(settings, tmpdir): 9 | settings.MEDIA_ROOT = tmpdir.strpath 10 | 11 | 12 | @pytest.fixture 13 | def user(db) -> User: 14 | return UserFactory() 15 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | {% block head_title %} 5 | {% endblock head_title %} 6 | {% endblock title %} 7 | {% block content %} 8 |
9 |
10 | {% block inner %}{% endblock inner %} 11 |
12 |
13 | {% endblock content %} 14 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/password_reset_from_key_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %} 6 | {% translate "Change Password" %} 7 | {% endblock head_title %} 8 | {% block inner %} 9 |

{% translate "Change Password" %}

10 |

{% translate "Your password is now changed." %}

11 | {% endblock inner %} 12 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/signup_closed.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %} 6 | {% translate "Sign Up Closed" %} 7 | {% endblock head_title %} 8 | {% block inner %} 9 |

{% translate "Sign Up Closed" %}

10 |

{% translate "We are sorry, but the sign up is currently closed." %}

11 | {% endblock inner %} 12 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Server Error{% endblock title %} 4 | {% block content %} 5 |

Ooops!!! 500

6 |

Looks like something went wrong!

7 |

8 | We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing. 9 |

10 | {% endblock content %} 11 | -------------------------------------------------------------------------------- /Phase_3/locale/en_US/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # Translations for the Resume Builder project 2 | # Copyright (C) 2024 Alireza Parvaresh 3 | # Alireza Parvaresh , 2024. 4 | # 5 | #, fuzzy 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: 0.1.0\n" 9 | "Language: en-US\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class UsersConfig(AppConfig): 6 | name = "resume_builder.users" 7 | verbose_name = _("Users") 8 | 9 | def ready(self): 10 | try: 11 | import resume_builder.users.signals # noqa: F401 12 | except ImportError: 13 | pass 14 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from resume_builder.users.views import user_detail_view, user_redirect_view, user_update_view 4 | 5 | app_name = "users" 6 | urlpatterns = [ 7 | path("~redirect/", view=user_redirect_view, name="redirect"), 8 | path("~update/", view=user_update_view, name="update"), 9 | path("/", view=user_detail_view, name="detail"), 10 | ] 11 | -------------------------------------------------------------------------------- /Phase_3/compose/production/postgres/maintenance/_sourced/yes_no.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | yes_no() { 5 | declare desc="Prompt for confirmation. \$\"\{1\}\": confirmation message." 6 | local arg1="${1}" 7 | 8 | local response= 9 | read -r -p "${arg1} (y/[n])? " response 10 | if [[ "${response}" =~ ^[Yy]$ ]] 11 | then 12 | exit 0 13 | else 14 | exit 1 15 | fi 16 | } 17 | -------------------------------------------------------------------------------- /Phase_3/compose/production/postgres/maintenance/_sourced/countdown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | countdown() { 5 | declare desc="A simple countdown. Source: https://superuser.com/a/611582" 6 | local seconds="${1}" 7 | local d=$(($(date +%s) + "${seconds}")) 8 | while [ "$d" -ge `date +%s` ]; do 9 | echo -ne "$(date -u --date @$(($d - `date +%s`)) +%H:%M:%S)\r"; 10 | sleep 0.1 11 | done 12 | } 13 | -------------------------------------------------------------------------------- /Phase_3/requirements/production.txt: -------------------------------------------------------------------------------- 1 | # PRECAUTION: avoid production dependencies that aren't in development 2 | 3 | -r base.txt 4 | 5 | gunicorn==21.2.0 # https://github.com/benoitc/gunicorn 6 | psycopg[c]==3.1.17 # https://github.com/psycopg/psycopg 7 | 8 | # Django 9 | # ------------------------------------------------------------------------------ 10 | django-anymail[mailgun]==10.2 # https://github.com/anymail/django-anymail 11 | -------------------------------------------------------------------------------- /Phase_3/setup.cfg: -------------------------------------------------------------------------------- 1 | # flake8 and pycodestyle don't support pyproject.toml 2 | # https://github.com/PyCQA/flake8/issues/234 3 | # https://github.com/PyCQA/pycodestyle/issues/813 4 | [flake8] 5 | max-line-length = 119 6 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv,.venv 7 | extend-ignore = E203 8 | 9 | [pycodestyle] 10 | max-line-length = 119 11 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv,.venv 12 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/static/css/thin_scroll.css: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar { 2 | width: 8px; 3 | height: 8px; 4 | } 5 | ::-webkit-scrollbar-track { 6 | -webkit-box-shadow: inset 0 0 6px hsla(0,0%,0%,0.3); 7 | border-radius: 0px; 8 | } 9 | ::-webkit-scrollbar-thumb { 10 | border-radius: 0px; 11 | background-color: hsla(0,0%,100%,0.5); 12 | -webkit-box-shadow: inset 0 0 6px hsla(0,0%,100%,1); 13 | } 14 | ::-webkit-scrollbar-thumb:hover { 15 | background-color: hsla(0,0%,100%,0.7); 16 | } -------------------------------------------------------------------------------- /Phase_3/docs/users.rst: -------------------------------------------------------------------------------- 1 | .. _users: 2 | 3 | Users 4 | ====================================================================== 5 | 6 | Starting a new project, it’s highly recommended to set up a custom user model, 7 | even if the default User model is sufficient for you. 8 | 9 | This model behaves identically to the default user model, 10 | but you’ll be able to customize it in the future if the need arises. 11 | 12 | .. automodule:: resume_builder.users.models 13 | :members: 14 | :noindex: 15 | 16 | -------------------------------------------------------------------------------- /Phase_3/config/api_router.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from rest_framework.routers import DefaultRouter, SimpleRouter 3 | 4 | from resume_builder.resume.urls import router as router_resume 5 | from resume_builder.users.api.views import UserViewSet 6 | 7 | if settings.DEBUG: 8 | router = DefaultRouter() 9 | else: 10 | router = SimpleRouter() 11 | 12 | router.register("users", UserViewSet) 13 | router.registry.extend(router_resume.registry) 14 | 15 | app_name = "api" 16 | urlpatterns = router.urls 17 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/api/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from rest_framework import serializers 3 | 4 | from resume_builder.users.models import User as UserType 5 | 6 | User = get_user_model() 7 | 8 | 9 | class UserSerializer(serializers.ModelSerializer[UserType]): 10 | class Meta: 11 | model = User 12 | fields = ["name", "url"] 13 | 14 | extra_kwargs = { 15 | "url": {"view_name": "api:user-detail", "lookup_field": "pk"}, 16 | } 17 | -------------------------------------------------------------------------------- /Phase_3/compose/production/postgres/maintenance/backups: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | ### View backups. 5 | ### 6 | ### Usage: 7 | ### $ docker compose -f .yml (exec |run --rm) postgres backups 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 "These are the backups you have got:" 21 | 22 | ls -lht "${BACKUP_DIR_PATH}" 23 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/resume/migrations/0003_resume_languages.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.9 on 2024-01-28 19:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("resume", "0002_resume_user_alter_education_degree_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="resume", 15 | name="languages", 16 | field=models.ManyToManyField(blank=True, to="resume.language"), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/verification_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %} 6 | {% translate "Verify Your E-mail Address" %} 7 | {% endblock head_title %} 8 | {% block inner %} 9 |

{% translate "Verify Your E-mail Address" %}

10 |

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 |

13 | {% endblock inner %} 14 | -------------------------------------------------------------------------------- /Phase_3/docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Resume Builder documentation master file, created by 2 | sphinx-quickstart. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Resume Builder's documentation! 7 | ====================================================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | howto 14 | users 15 | 16 | 17 | 18 | Indices and tables 19 | ================== 20 | 21 | * :ref:`genindex` 22 | * :ref:`modindex` 23 | * :ref:`search` 24 | -------------------------------------------------------------------------------- /Phase_3/resume-generator-gh-pages/compress_pdf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | dir_path = "/home/reza/Desktop/test/" 5 | 6 | # Make sure dir_path is ending in / 7 | num_of_pages = 2 8 | out_file = dir_path+"14010XXXX_CV.pdf" 9 | 10 | 11 | cmd = "gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/screen -dNOPAUSE -dQUIET -dBATCH -sOutputFile="+out_file 12 | # cmd = "gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/printer -dNOPAUSE -dQUIET -dBATCH -sOutputFile="+out_file 13 | for i in range(1,num_of_pages+1): 14 | filename = dir_path+str(i)+".pdf" 15 | cmd = cmd+" "+filename 16 | 17 | os.system(cmd) 18 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %} 7 | {% translate "Password Reset" %} 8 | {% endblock head_title %} 9 | {% block inner %} 10 |

{% translate "Password Reset" %}

11 | {% if user.is_authenticated %} 12 | {% include "account/snippets/already_logged_in.html" %} 13 | {% endif %} 14 |

15 | {% blocktranslate %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktranslate %} 16 |

17 | {% endblock inner %} 18 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/contrib/sites/migrations/0004_alter_options_ordering_domain.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2021-02-04 14:49 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("sites", "0003_set_site_domain_and_name"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="site", 15 | options={ 16 | "ordering": ["domain"], 17 | "verbose_name": "site", 18 | "verbose_name_plural": "sites", 19 | }, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %} 7 | {% translate "Change Password" %} 8 | {% endblock head_title %} 9 | {% block inner %} 10 |

{% translate "Change Password" %}

11 |
14 | {% csrf_token %} 15 | {{ form|crispy }} 16 | 17 |
18 | {% endblock inner %} 19 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/users/user_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %} 6 | 7 | 8 | {{ user.name }} 9 | 10 | 11 | {% endblock title %} 12 | {% block content %} 13 |

14 | 15 | 16 | {{ user.name }} 17 | 18 | 19 |

20 |
23 | {% csrf_token %} 24 | {{ form|crispy }} 25 |
26 |
27 | 28 |
29 |
30 |
31 | {% endblock content %} 32 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/password_set.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %} 7 | {% translate "Set Password" %} 8 | {% endblock head_title %} 9 | {% block inner %} 10 |

{% translate "Set Password" %}

11 |
14 | {% csrf_token %} 15 | {{ form|crispy }} 16 | 20 |
21 | {% endblock inner %} 22 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/tests/test_urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import resolve, reverse 2 | 3 | from resume_builder.users.models import User 4 | 5 | 6 | def test_detail(user: User): 7 | assert reverse("users:detail", kwargs={"pk": user.pk}) == f"/users/{user.pk}/" 8 | assert resolve(f"/users/{user.pk}/").view_name == "users:detail" 9 | 10 | 11 | def test_update(): 12 | assert reverse("users:update") == "/users/~update/" 13 | assert resolve("/users/~update/").view_name == "users:update" 14 | 15 | 16 | def test_redirect(): 17 | assert reverse("users:redirect") == "/users/~redirect/" 18 | assert resolve("/users/~redirect/").view_name == "users:redirect" 19 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/contrib/sites/migrations/0002_alter_domain_unique.py: -------------------------------------------------------------------------------- 1 | import django.contrib.sites.models 2 | from django.db import migrations, models 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [("sites", "0001_initial")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="site", 12 | name="domain", 13 | field=models.CharField( 14 | max_length=100, 15 | unique=True, 16 | validators=[django.contrib.sites.models._simple_domain_name_validator], 17 | verbose_name="domain name", 18 | ), 19 | ) 20 | ] 21 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/tests/test_drf_urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import resolve, reverse 2 | 3 | from resume_builder.users.models import User 4 | 5 | 6 | def test_user_detail(user: User): 7 | assert reverse("api:user-detail", kwargs={"pk": user.pk}) == f"/api/users/{user.pk}/" 8 | assert resolve(f"/api/users/{user.pk}/").view_name == "api:user-detail" 9 | 10 | 11 | def test_user_list(): 12 | assert reverse("api:user-list") == "/api/users/" 13 | assert resolve("/api/users/").view_name == "api:user-list" 14 | 15 | 16 | def test_user_me(): 17 | assert reverse("api:user-me") == "/api/users/me/" 18 | assert resolve("/api/users/me/").view_name == "api:user-me" 19 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/tests/test_swagger.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.urls import reverse 3 | 4 | 5 | def test_swagger_accessible_by_admin(admin_client): 6 | url = reverse("api-docs") 7 | response = admin_client.get(url) 8 | assert response.status_code == 200 9 | 10 | 11 | @pytest.mark.django_db 12 | def test_swagger_ui_not_accessible_by_normal_user(client): 13 | url = reverse("api-docs") 14 | response = client.get(url) 15 | assert response.status_code == 403 16 | 17 | 18 | def test_api_schema_generated_successfully(admin_client): 19 | url = reverse("api-schema") 20 | response = admin_client.get(url) 21 | assert response.status_code == 200 22 | -------------------------------------------------------------------------------- /Phase_3/compose/production/postgres/maintenance/_sourced/messages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | message_newline() { 5 | echo 6 | } 7 | 8 | message_debug() 9 | { 10 | echo -e "DEBUG: ${@}" 11 | } 12 | 13 | message_welcome() 14 | { 15 | echo -e "\e[1m${@}\e[0m" 16 | } 17 | 18 | message_warning() 19 | { 20 | echo -e "\e[33mWARNING\e[0m: ${@}" 21 | } 22 | 23 | message_error() 24 | { 25 | echo -e "\e[31mERROR\e[0m: ${@}" 26 | } 27 | 28 | message_info() 29 | { 30 | echo -e "\e[37mINFO\e[0m: ${@}" 31 | } 32 | 33 | message_suggestion() 34 | { 35 | echo -e "\e[33mSUGGESTION\e[0m: ${@}" 36 | } 37 | 38 | message_success() 39 | { 40 | echo -e "\e[32mSUCCESS\e[0m: ${@}" 41 | } 42 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/resume/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from rest_framework import routers 3 | 4 | from .views import EducationViewSet, ExperienceViewSet, LanguageViewSet, ProjectViewSet, ResumeViewSet, SkillViewSet 5 | 6 | router = routers.DefaultRouter() if settings.DEBUG else routers.SimpleRouter() 7 | 8 | router.register("resumes", ResumeViewSet, basename="resume") 9 | router.register("educations", EducationViewSet, basename="education") 10 | router.register("experiences", ExperienceViewSet, basename="experience") 11 | router.register("skills", SkillViewSet, basename="skill") 12 | router.register("projects", ProjectViewSet, basename="project") 13 | router.register("languages", LanguageViewSet, basename="language") 14 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %} 6 | {% translate "Sign Out" %} 7 | {% endblock head_title %} 8 | {% block inner %} 9 |

{% translate "Sign Out" %}

10 |

{% translate "Are you sure you want to sign out?" %}

11 |
12 | {% csrf_token %} 13 | {% if redirect_field_value %} 14 | 17 | {% endif %} 18 | 19 |
20 | {% endblock inner %} 21 | -------------------------------------------------------------------------------- /Phase_3/merge_production_dotenvs_in_dotenv.py: -------------------------------------------------------------------------------- 1 | import os 2 | from collections.abc import Sequence 3 | from pathlib import Path 4 | 5 | BASE_DIR = Path(__file__).parent.resolve() 6 | PRODUCTION_DOTENVS_DIR = BASE_DIR / ".envs" / ".production" 7 | PRODUCTION_DOTENV_FILES = [ 8 | PRODUCTION_DOTENVS_DIR / ".django", 9 | PRODUCTION_DOTENVS_DIR / ".postgres", 10 | ] 11 | DOTENV_FILE = BASE_DIR / ".env" 12 | 13 | 14 | def merge( 15 | output_file: Path, 16 | files_to_merge: Sequence[Path], 17 | ) -> None: 18 | merged_content = "" 19 | for merge_file in files_to_merge: 20 | merged_content += merge_file.read_text() 21 | merged_content += os.linesep 22 | output_file.write_text(merged_content) 23 | 24 | 25 | if __name__ == "__main__": 26 | merge(DOTENV_FILE, PRODUCTION_DOTENV_FILES) 27 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %} 7 | {% translate "Signup" %} 8 | {% endblock head_title %} 9 | {% block inner %} 10 |

{% translate "Sign Up" %}

11 |

12 | {% blocktranslate %}Already have an account? Then please sign in.{% endblocktranslate %} 13 |

14 | 27 | {% endblock inner %} 28 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/resume/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm 2 | 3 | from .models import Education, Experience, Language, Project, Resume, Skill 4 | 5 | 6 | class ResumeForm(ModelForm): 7 | class Meta: 8 | model = Resume 9 | fields = '__all__' 10 | 11 | class EducationForm(ModelForm): 12 | class Meta: 13 | model = Education 14 | fields = '__all__' 15 | 16 | class ExperienceForm(ModelForm): 17 | class Meta: 18 | model = Experience 19 | fields = '__all__' 20 | 21 | class SkillForm(ModelForm): 22 | class Meta: 23 | model = Skill 24 | fields = '__all__' 25 | 26 | class ProjectForm(ModelForm): 27 | class Meta: 28 | model = Project 29 | fields = '__all__' 30 | 31 | class LanguageForm(ModelForm): 32 | class Meta: 33 | model = Language 34 | fields = '__all__' 35 | 36 | class ResumeForm(ModelForm): 37 | class Meta: 38 | model = Resume 39 | fields = '__all__' 40 | 41 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/users/user_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% block title %} 6 | User: 7 | 8 | {{ object.name }} 9 | 10 | 11 | {% endblock title %} 12 | {% block content %} 13 |
14 |
15 |
16 |

17 | 18 | 19 | {{ object.name }} 20 | 21 |

22 |
23 |
24 | {% if object == request.user %} 25 | 26 |
27 |
28 | My Info 29 | E-Mail 32 | 33 |
34 |
35 | 36 | {% endif %} 37 |
38 | {% endblock content %} 39 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/verified_email_required.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %} 6 | {% translate "Verify Your E-mail Address" %} 7 | {% endblock head_title %} 8 | {% block inner %} 9 |

{% translate "Verify Your E-mail Address" %}

10 | {% url 'account_email' as email_url %} 11 |

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 |

21 |

22 | {% blocktranslate %}Note: you can still change your e-mail address.{% endblocktranslate %} 23 |

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 |
21 | {% csrf_token %} 22 | {{ form|crispy }} 23 | 26 |
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 |

16 |
18 | {% csrf_token %} 19 | 20 |
21 | {% else %} 22 | {% url 'account_email' as email_url %} 23 |

24 | {% blocktranslate %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktranslate %} 25 |

26 | {% endif %} 27 | {% endblock inner %} 28 | -------------------------------------------------------------------------------- /Phase_3/docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | 8 | if "%SPHINXBUILD%" == "" ( 9 | set SPHINXBUILD=sphinx-build -c . 10 | ) 11 | set SOURCEDIR=_source 12 | set BUILDDIR=_build 13 | set APP=..\resume_builder 14 | 15 | if "%1" == "" goto help 16 | 17 | %SPHINXBUILD% >NUL 2>NUL 18 | if errorlevel 9009 ( 19 | echo. 20 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 21 | echo.installed, then set the SPHINXBUILD environment variable to point 22 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 23 | echo.may add the Sphinx directory to PATH. 24 | echo. 25 | echo.Install sphinx-autobuild for live serving. 26 | echo.If you don't have Sphinx installed, grab it from 27 | echo.http://sphinx-doc.org/ 28 | exit /b 1 29 | ) 30 | 31 | %SPHINXBUILD% -b %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 32 | goto end 33 | 34 | :livehtml 35 | sphinx-autobuild -b html --open-browser -p 9000 --watch %APP% -c . %SOURCEDIR% %BUILDDIR%/html 36 | GOTO :EOF 37 | 38 | :apidocs 39 | sphinx-apidoc -o %SOURCEDIR%/api %APP% 40 | GOTO :EOF 41 | 42 | :help 43 | %SPHINXBUILD% -b help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 44 | 45 | :end 46 | popd 47 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db.models import CharField, EmailField 3 | from django.urls import reverse 4 | from django.utils.translation import gettext_lazy as _ 5 | 6 | from resume_builder.users.managers import UserManager 7 | 8 | 9 | class User(AbstractUser): 10 | """ 11 | Default custom user model for Resume Builder. 12 | If adding fields that need to be filled at user signup, 13 | check forms.SignupForm and forms.SocialSignupForms accordingly. 14 | """ 15 | 16 | # First and last name do not cover name patterns around the globe 17 | name = CharField(_("Name of User"), blank=True, max_length=255) 18 | first_name = None # type: ignore 19 | last_name = None # type: ignore 20 | email = EmailField(_("email address"), unique=True) 21 | username = None # type: ignore 22 | 23 | USERNAME_FIELD = "email" 24 | REQUIRED_FIELDS = [] 25 | 26 | objects = UserManager() 27 | 28 | def get_absolute_url(self) -> str: 29 | """Get URL for user's detail view. 30 | 31 | Returns: 32 | str: URL for user detail. 33 | 34 | """ 35 | return reverse("users:detail", kwargs={"pk": self.id}) 36 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/password_reset_from_key.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %} 7 | {% translate "Change Password" %} 8 | {% endblock head_title %} 9 | {% block inner %} 10 |

11 | {% if token_fail %} 12 | {% translate "Bad Token" %} 13 | {% else %} 14 | {% translate "Change Password" %} 15 | {% endif %} 16 |

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 |
25 | {% csrf_token %} 26 | {{ form|crispy }} 27 | 31 |
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 |

{{ education.institution_name }} - {{ education.degree }}

16 |

{{ education.start_date }} - {% if education.end_date %}{{ education.end_date }}{% else %}Present{% endif %}

17 |

{{ education.description }}

18 | {% endfor %} 19 | 20 |

Experience

21 | {% for experience in user.experience_set.all %} 22 |

{{ experience.company_name }} - {{ experience.job_title }}

23 |

{{ experience.start_date }} - {% if experience.end_date %}{{ experience.end_date }}{% else %}Present{% endif %}

24 |

{{ experience.description }}

25 | {% endfor %} 26 | 27 |

Skills

28 |
    29 | {% for skill in user.skill_set.all %} 30 |
  • {{ skill.skill_name }} - {{ skill.skill_description }}
  • 31 | {% endfor %} 32 |
33 | 34 |

Projects

35 |
    36 | {% for project in user.project_set.all %} 37 |
  • {{ project.project_name }} - {{ project.project_description }}
  • 38 | {% endfor %} 39 |
40 | 41 |

Languages

42 |
    43 | {% for language in user.language_set.all %} 44 |
  • {{ language.language }} - Level: {{ language.language_level }} - Native: {% if language.is_native %}Yes{% else %}No{% endif %}
  • 45 | {% endfor %} 46 |
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 | 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 |

38 | {% endif %} 39 | {% endif %} 40 | 52 | {% endblock inner %} 53 | -------------------------------------------------------------------------------- /Phase_3/compose/local/docs/Dockerfile: -------------------------------------------------------------------------------- 1 | # define an alias for the specific python version used in this file. 2 | FROM docker.io/python:3.11.7-slim-bookworm as python 3 | 4 | 5 | # Python build stage 6 | FROM docker.io/python as python-build-stage 7 | 8 | ENV PYTHONDONTWRITEBYTECODE 1 9 | 10 | RUN apt-get update && apt-get install --no-install-recommends -y \ 11 | # dependencies for building Python packages 12 | build-essential \ 13 | # psycopg2 dependencies 14 | libpq-dev \ 15 | # cleaning up unused files 16 | && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ 17 | && rm -rf /var/lib/apt/lists/* 18 | 19 | # Requirements are installed here to ensure they will be cached. 20 | COPY ./requirements /requirements 21 | 22 | # create python dependency wheels 23 | RUN pip wheel --no-cache-dir --wheel-dir /usr/src/app/wheels \ 24 | -r /requirements/local.txt -r /requirements/production.txt \ 25 | && rm -rf /requirements 26 | 27 | 28 | # Python 'run' stage 29 | FROM docker.io/python as python-run-stage 30 | 31 | ARG BUILD_ENVIRONMENT 32 | ENV PYTHONUNBUFFERED 1 33 | ENV PYTHONDONTWRITEBYTECODE 1 34 | 35 | RUN apt-get update && apt-get install --no-install-recommends -y \ 36 | # To run the Makefile 37 | make \ 38 | # psycopg2 dependencies 39 | libpq-dev \ 40 | # Translations dependencies 41 | gettext \ 42 | # Uncomment below lines to enable Sphinx output to latex and pdf 43 | # texlive-latex-recommended \ 44 | # texlive-fonts-recommended \ 45 | # texlive-latex-extra \ 46 | # latexmk \ 47 | # cleaning up unused files 48 | && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ 49 | && rm -rf /var/lib/apt/lists/* 50 | 51 | # copy python dependency wheels from python-build-stage 52 | COPY --from=python-build-stage /usr/src/app/wheels /wheels 53 | 54 | # use wheels to install python dependencies 55 | RUN pip install --no-cache /wheels/* \ 56 | && rm -rf /wheels 57 | 58 | COPY ./compose/local/docs/start /start-docs 59 | RUN sed -i 's/\r$//g' /start-docs 60 | RUN chmod +x /start-docs 61 | 62 | WORKDIR /docs 63 | -------------------------------------------------------------------------------- /Phase_3/pyproject.toml: -------------------------------------------------------------------------------- 1 | # ==== pytest ==== 2 | [tool.pytest.ini_options] 3 | minversion = "6.0" 4 | addopts = "--ds=config.settings.test --reuse-db" 5 | python_files = [ 6 | "tests.py", 7 | "test_*.py", 8 | ] 9 | 10 | # ==== Coverage ==== 11 | [tool.coverage.run] 12 | include = ["resume_builder/**"] 13 | omit = ["*/migrations/*", "*/tests/*"] 14 | plugins = ["django_coverage_plugin"] 15 | 16 | 17 | # ==== black ==== 18 | [tool.black] 19 | line-length = 119 20 | target-version = ['py311'] 21 | 22 | 23 | # ==== isort ==== 24 | [tool.isort] 25 | profile = "black" 26 | line_length = 119 27 | known_first_party = [ 28 | "resume_builder", 29 | "config", 30 | ] 31 | skip = ["venv/"] 32 | skip_glob = ["**/migrations/*.py"] 33 | 34 | 35 | # ==== mypy ==== 36 | [tool.mypy] 37 | python_version = "3.11" 38 | check_untyped_defs = true 39 | ignore_missing_imports = true 40 | warn_unused_ignores = true 41 | warn_redundant_casts = true 42 | warn_unused_configs = true 43 | plugins = [ 44 | "mypy_django_plugin.main", 45 | "mypy_drf_plugin.main", 46 | ] 47 | 48 | [[tool.mypy.overrides]] 49 | # Django migrations should not produce any errors: 50 | module = "*.migrations.*" 51 | ignore_errors = true 52 | 53 | [tool.django-stubs] 54 | django_settings_module = "config.settings.test" 55 | 56 | 57 | # ==== PyLint ==== 58 | [tool.pylint.MASTER] 59 | load-plugins = [ 60 | "pylint_django", 61 | ] 62 | django-settings-module = "config.settings.local" 63 | 64 | [tool.pylint.FORMAT] 65 | max-line-length = 119 66 | 67 | [tool.pylint."MESSAGES CONTROL"] 68 | disable = [ 69 | "missing-docstring", 70 | "invalid-name", 71 | ] 72 | 73 | [tool.pylint.DESIGN] 74 | max-parents = 13 75 | 76 | [tool.pylint.TYPECHECK] 77 | generated-members = [ 78 | "REQUEST", 79 | "acl_users", 80 | "aq_parent", 81 | "[a-zA-Z]+_set{1,2}", 82 | "save", 83 | "delete", 84 | ] 85 | 86 | 87 | # ==== djLint ==== 88 | [tool.djlint] 89 | blank_line_after_tag = "load,extends" 90 | close_void_tags = true 91 | format_css = true 92 | format_js = true 93 | # TODO: remove T002 when fixed https://github.com/Riverside-Healthcare/djLint/issues/687 94 | ignore = "H006,H030,H031,T002" 95 | include = "H017,H035" 96 | indent = 2 97 | max_line_length = 119 98 | profile = "django" 99 | 100 | [tool.djlint.css] 101 | indent_size = 2 102 | 103 | [tool.djlint.js] 104 | indent_size = 2 105 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/contrib/sites/migrations/0003_set_site_domain_and_name.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | 7 | from django.conf import settings 8 | from django.db import migrations 9 | 10 | 11 | def _update_or_create_site_with_sequence(site_model, connection, domain, name): 12 | """Update or create the site with default ID and keep the DB sequence in sync.""" 13 | site, created = site_model.objects.update_or_create( 14 | id=settings.SITE_ID, 15 | defaults={ 16 | "domain": domain, 17 | "name": name, 18 | }, 19 | ) 20 | if created: 21 | # We provided the ID explicitly when creating the Site entry, therefore the DB 22 | # sequence to auto-generate them wasn't used and is now out of sync. If we 23 | # don't do anything, we'll get a unique constraint violation the next time a 24 | # site is created. 25 | # To avoid this, we need to manually update DB sequence and make sure it's 26 | # greater than the maximum value. 27 | max_id = site_model.objects.order_by("-id").first().id 28 | with connection.cursor() as cursor: 29 | cursor.execute("SELECT last_value from django_site_id_seq") 30 | (current_id,) = cursor.fetchone() 31 | if current_id <= max_id: 32 | cursor.execute( 33 | "alter sequence django_site_id_seq restart with %s", 34 | [max_id + 1], 35 | ) 36 | 37 | 38 | def update_site_forward(apps, schema_editor): 39 | """Set site domain and name.""" 40 | Site = apps.get_model("sites", "Site") 41 | _update_or_create_site_with_sequence( 42 | Site, 43 | schema_editor.connection, 44 | "example.com", 45 | "Resume Builder", 46 | ) 47 | 48 | 49 | def update_site_backward(apps, schema_editor): 50 | """Revert site domain and name to default.""" 51 | Site = apps.get_model("sites", "Site") 52 | _update_or_create_site_with_sequence( 53 | Site, 54 | schema_editor.connection, 55 | "example.com", 56 | "example.com", 57 | ) 58 | 59 | 60 | class Migration(migrations.Migration): 61 | 62 | dependencies = [("sites", "0002_alter_domain_unique")] 63 | 64 | operations = [migrations.RunPython(update_site_forward, update_site_backward)] 65 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/static/css/theme.css: -------------------------------------------------------------------------------- 1 | ul.nobullet { 2 | list-style: none; 3 | } 4 | ul.disc { 5 | list-style: disc; 6 | margin-left: 0px; 7 | padding-left: 20px; 8 | } 9 | ul.circle { 10 | list-style: circle; 11 | margin-left: 0px; 12 | padding-left: 20px; 13 | } 14 | ul.square { 15 | list-style: square; 16 | margin-left: 0px; 17 | padding-left: 20px; 18 | } 19 | ul.dash { 20 | list-style: none; 21 | padding-left: 0px; 22 | margin-left: 0px; 23 | } 24 | ul.dash li { 25 | padding-left: 20px; 26 | text-indent: -1em; 27 | } 28 | ul.dash li:before { 29 | content: "-"; 30 | padding-right: 10px; 31 | } 32 | ul.decimal { 33 | list-style: decimal; 34 | margin-left: 0px; 35 | padding-left: 20px; 36 | } 37 | ul.upper-roman { 38 | list-style: upper-roman; 39 | margin-left: 0px; 40 | padding-left: 20px; 41 | } 42 | ul.lower-roman { 43 | list-style: lower-roman; 44 | margin-left: 0px; 45 | padding-left: 20px; 46 | } 47 | ul.upper-alpha { 48 | list-style: upper-alpha; 49 | margin-left: 0px; 50 | padding-left: 20px; 51 | } 52 | ul.lower-alpha { 53 | list-style: lower-alpha; 54 | margin-left: 0px; 55 | padding-left: 20px; 56 | } 57 | 58 | 59 | .corner { 60 | border-radius: 0px; 61 | } 62 | 63 | .toggle-button { 64 | display: inline-block; 65 | float: right; 66 | } 67 | .toggle-option { 68 | display: inline-block; 69 | padding: 3px; 70 | padding-left: 8px; 71 | padding-right: 8px; 72 | cursor: pointer; 73 | border-radius: 2px; 74 | } 75 | .toggle-option { 76 | background-color: #222; 77 | } 78 | .toggle-option.selected { 79 | /* background-color: #27AE60; */ 80 | /* background-color: #5cb85c; */ 81 | background-color: #E74C3C; 82 | } 83 | 84 | 85 | .custom-button { 86 | display: inline-block; 87 | padding: 3px; 88 | padding-top: 0px; 89 | padding-bottom: 0px; 90 | cursor: pointer; 91 | border-radius: 2px; 92 | } 93 | .custom-button { 94 | background-color: #8E44AD; 95 | } 96 | .custom-button.selected { 97 | background-color: #E74C3C; 98 | } 99 | 100 | 101 | 102 | .btn-group { 103 | display: inline-block; 104 | padding: 3px; 105 | padding-top: 0px; 106 | padding-bottom: 0px; 107 | cursor: pointer; 108 | border-radius: 2px; 109 | } 110 | 111 | .btn-violet { 112 | background-color: #8E44AD; 113 | } 114 | .btn-teal { 115 | background-color: #16a085; 116 | } 117 | 118 | 119 | 120 | kbd { 121 | padding: 0; 122 | padding-left: 3px; 123 | padding-right: 3px; 124 | background-color: #666; 125 | } -------------------------------------------------------------------------------- /Phase_3/docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | 13 | import os 14 | import sys 15 | 16 | import django 17 | 18 | if os.getenv("READTHEDOCS", default=False) == "True": 19 | sys.path.insert(0, os.path.abspath("..")) 20 | os.environ["DJANGO_READ_DOT_ENV_FILE"] = "True" 21 | os.environ["USE_DOCKER"] = "no" 22 | else: 23 | sys.path.insert(0, os.path.abspath("/app")) 24 | os.environ["DATABASE_URL"] = "sqlite:///readthedocs.db" 25 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") 26 | django.setup() 27 | 28 | # -- Project information ----------------------------------------------------- 29 | 30 | project = "Resume Builder" 31 | copyright = """2024, Alireza Parvaresh""" 32 | author = "Alireza Parvaresh" 33 | 34 | 35 | # -- General configuration --------------------------------------------------- 36 | 37 | # Add any Sphinx extension module names here, as strings. They can be 38 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 39 | # ones. 40 | extensions = [ 41 | "sphinx.ext.autodoc", 42 | "sphinx.ext.napoleon", 43 | ] 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | # templates_path = ["_templates"] 47 | 48 | # List of patterns, relative to source directory, that match files and 49 | # directories to ignore when looking for source files. 50 | # This pattern also affects html_static_path and html_extra_path. 51 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 52 | 53 | # -- Options for HTML output ------------------------------------------------- 54 | 55 | # The theme to use for HTML and HTML Help pages. See the documentation for 56 | # a list of builtin themes. 57 | # 58 | html_theme = "alabaster" 59 | 60 | # Add any paths that contain custom static files (such as style sheets) here, 61 | # relative to this directory. They are copied after the builtin static files, 62 | # so a file named "default.css" will overwrite the builtin "default.css". 63 | # html_static_path = ["_static"] 64 | -------------------------------------------------------------------------------- /Phase_3/compose/production/django/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | # define an alias for the specific python version used in this file. 3 | FROM docker.io/python:3.11.7-slim-bookworm as python 4 | 5 | # Python build stage 6 | FROM docker.io/python as python-build-stage 7 | 8 | ARG BUILD_ENVIRONMENT=production 9 | 10 | # Install apt packages 11 | RUN apt-get update && apt-get install --no-install-recommends -y \ 12 | # dependencies for building Python packages 13 | build-essential \ 14 | # psycopg2 dependencies 15 | libpq-dev 16 | 17 | # Requirements are installed here to ensure they will be cached. 18 | COPY ./requirements . 19 | 20 | # Create Python Dependency and Sub-Dependency Wheels. 21 | RUN pip wheel --wheel-dir /usr/src/app/wheels \ 22 | -r ${BUILD_ENVIRONMENT}.txt 23 | 24 | 25 | # Python 'run' stage 26 | FROM docker.io/python as python-run-stage 27 | 28 | ARG BUILD_ENVIRONMENT=production 29 | ARG APP_HOME=/app 30 | 31 | ENV PYTHONUNBUFFERED 1 32 | ENV PYTHONDONTWRITEBYTECODE 1 33 | ENV BUILD_ENV ${BUILD_ENVIRONMENT} 34 | 35 | WORKDIR ${APP_HOME} 36 | 37 | RUN addgroup --system django \ 38 | && adduser --system --ingroup django django 39 | 40 | 41 | # Install required system dependencies 42 | RUN apt-get update && apt-get install --no-install-recommends -y \ 43 | # psycopg2 dependencies 44 | libpq-dev \ 45 | # Translations dependencies 46 | gettext \ 47 | # cleaning up unused files 48 | && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ 49 | && rm -rf /var/lib/apt/lists/* 50 | 51 | # All absolute dir copies ignore workdir instruction. All relative dir copies are wrt to the workdir instruction 52 | # copy python dependency wheels from python-build-stage 53 | COPY --from=python-build-stage /usr/src/app/wheels /wheels/ 54 | 55 | # use wheels to install python dependencies 56 | RUN pip install --no-cache-dir --no-index --find-links=/wheels/ /wheels/* \ 57 | && rm -rf /wheels/ 58 | 59 | 60 | COPY --chown=django:django ./compose/production/django/entrypoint /entrypoint 61 | RUN sed -i 's/\r$//g' /entrypoint 62 | RUN chmod +x /entrypoint 63 | 64 | 65 | COPY --chown=django:django ./compose/production/django/start /start 66 | RUN sed -i 's/\r$//g' /start 67 | RUN chmod +x /start 68 | 69 | 70 | # copy application code to WORKDIR 71 | COPY --chown=django:django . ${APP_HOME} 72 | 73 | # make django owner of the WORKDIR directory as well. 74 | RUN chown django:django ${APP_HOME} 75 | 76 | USER django 77 | 78 | RUN DATABASE_URL="" \ 79 | DJANGO_SETTINGS_MODULE="config.settings.test" \ 80 | python manage.py compilemessages 81 | 82 | ENTRYPOINT ["/entrypoint"] 83 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/users/tests/test_admin.py: -------------------------------------------------------------------------------- 1 | from importlib import reload 2 | 3 | import pytest 4 | from django.contrib import admin 5 | from django.contrib.auth.models import AnonymousUser 6 | from django.urls import reverse 7 | from pytest_django.asserts import assertRedirects 8 | 9 | from resume_builder.users.models import User 10 | 11 | 12 | class TestUserAdmin: 13 | def test_changelist(self, admin_client): 14 | url = reverse("admin:users_user_changelist") 15 | response = admin_client.get(url) 16 | assert response.status_code == 200 17 | 18 | def test_search(self, admin_client): 19 | url = reverse("admin:users_user_changelist") 20 | response = admin_client.get(url, data={"q": "test"}) 21 | assert response.status_code == 200 22 | 23 | def test_add(self, admin_client): 24 | url = reverse("admin:users_user_add") 25 | response = admin_client.get(url) 26 | assert response.status_code == 200 27 | 28 | response = admin_client.post( 29 | url, 30 | data={ 31 | "email": "new-admin@example.com", 32 | "password1": "My_R@ndom-P@ssw0rd", 33 | "password2": "My_R@ndom-P@ssw0rd", 34 | }, 35 | ) 36 | assert response.status_code == 302 37 | assert User.objects.filter(email="new-admin@example.com").exists() 38 | 39 | def test_view_user(self, admin_client): 40 | user = User.objects.get(email="admin@example.com") 41 | url = reverse("admin:users_user_change", kwargs={"object_id": user.pk}) 42 | response = admin_client.get(url) 43 | assert response.status_code == 200 44 | 45 | @pytest.fixture 46 | def force_allauth(self, settings): 47 | settings.DJANGO_ADMIN_FORCE_ALLAUTH = True 48 | # Reload the admin module to apply the setting change 49 | import resume_builder.users.admin as users_admin # pylint: disable=import-outside-toplevel 50 | 51 | try: 52 | reload(users_admin) 53 | except admin.sites.AlreadyRegistered: 54 | pass 55 | 56 | @pytest.mark.django_db 57 | @pytest.mark.usefixtures("force_allauth") 58 | def test_allauth_login(self, rf, settings): 59 | request = rf.get("/fake-url") 60 | request.user = AnonymousUser() 61 | response = admin.site.login(request) 62 | 63 | # The `admin` login view should redirect to the `allauth` login view 64 | target_url = reverse(settings.LOGIN_URL) + "?next=" + request.path 65 | assertRedirects(response, target_url, fetch_redirect_response=False) 66 | -------------------------------------------------------------------------------- /Phase_3/compose/local/django/Dockerfile: -------------------------------------------------------------------------------- 1 | # define an alias for the specific python version used in this file. 2 | FROM docker.io/python:3.11.7-slim-bookworm as python 3 | 4 | # Python build stage 5 | FROM docker.io/python as python-build-stage 6 | 7 | ARG BUILD_ENVIRONMENT=local 8 | 9 | # Install apt packages 10 | RUN apt-get update && apt-get install --no-install-recommends -y \ 11 | # dependencies for building Python packages 12 | build-essential \ 13 | # psycopg2 dependencies 14 | libpq-dev 15 | 16 | # Requirements are installed here to ensure they will be cached. 17 | COPY ./requirements . 18 | 19 | # Create Python Dependency and Sub-Dependency Wheels. 20 | RUN pip wheel --wheel-dir /usr/src/app/wheels \ 21 | -r ${BUILD_ENVIRONMENT}.txt 22 | 23 | 24 | # Python 'run' stage 25 | FROM docker.io/python as python-run-stage 26 | 27 | ARG BUILD_ENVIRONMENT=local 28 | ARG APP_HOME=/app 29 | 30 | ENV PYTHONUNBUFFERED 1 31 | ENV PYTHONDONTWRITEBYTECODE 1 32 | ENV BUILD_ENV ${BUILD_ENVIRONMENT} 33 | 34 | WORKDIR ${APP_HOME} 35 | 36 | 37 | # devcontainer dependencies and utils 38 | RUN apt-get update && apt-get install --no-install-recommends -y \ 39 | sudo git bash-completion nano ssh 40 | 41 | # Create devcontainer user and add it to sudoers 42 | RUN groupadd --gid 1000 dev-user \ 43 | && useradd --uid 1000 --gid dev-user --shell /bin/bash --create-home dev-user \ 44 | && echo dev-user ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/dev-user \ 45 | && chmod 0440 /etc/sudoers.d/dev-user 46 | 47 | 48 | # Install required system dependencies 49 | RUN apt-get update && apt-get install --no-install-recommends -y \ 50 | # psycopg2 dependencies 51 | libpq-dev \ 52 | # Translations dependencies 53 | gettext \ 54 | # cleaning up unused files 55 | && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ 56 | && rm -rf /var/lib/apt/lists/* 57 | 58 | # All absolute dir copies ignore workdir instruction. All relative dir copies are wrt to the workdir instruction 59 | # copy python dependency wheels from python-build-stage 60 | COPY --from=python-build-stage /usr/src/app/wheels /wheels/ 61 | 62 | # use wheels to install python dependencies 63 | RUN pip install --no-cache-dir --no-index --find-links=/wheels/ /wheels/* \ 64 | && rm -rf /wheels/ 65 | 66 | COPY ./compose/production/django/entrypoint /entrypoint 67 | RUN sed -i 's/\r$//g' /entrypoint 68 | RUN chmod +x /entrypoint 69 | 70 | COPY ./compose/local/django/start /start 71 | RUN sed -i 's/\r$//g' /start 72 | RUN chmod +x /start 73 | 74 | 75 | 76 | # copy application code to WORKDIR 77 | COPY . ${APP_HOME} 78 | 79 | ENTRYPOINT ["/entrypoint"] 80 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/resume/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.db import models 3 | 4 | User = get_user_model() 5 | 6 | 7 | class Experience(models.Model): 8 | company_name = models.CharField(max_length=100, null=True, blank=True) 9 | job_title = models.CharField(max_length=100, null=True, blank=True) 10 | start_date = models.DateField(null=True, blank=True) 11 | end_date = models.DateField(null=True, blank=True) 12 | description = models.TextField(null=True, blank=True) 13 | 14 | def __str__(self): 15 | return f"{self.company_name} - {self.job_title}" 16 | 17 | 18 | class Education(models.Model): 19 | institution_name = models.CharField(max_length=100, null=True, blank=True) 20 | degree = models.CharField(max_length=100, null=True, blank=True) 21 | start_date = models.DateField(null=True, blank=True) 22 | end_date = models.DateField(null=True, blank=True) 23 | description = models.TextField(null=True, blank=True) 24 | 25 | def __str__(self): 26 | return f"{self.institution_name} - {self.degree}" 27 | 28 | 29 | class Skill(models.Model): 30 | skill_name = models.CharField(max_length=100, null=True, blank=True) 31 | skill_description = models.TextField(null=True, blank=True) 32 | 33 | def __str__(self): 34 | return self.skill_name 35 | 36 | 37 | class Project(models.Model): 38 | project_name = models.CharField(max_length=100, null=True, blank=True) 39 | project_description = models.TextField(null=True, blank=True) 40 | 41 | def __str__(self): 42 | return self.project_name 43 | 44 | 45 | class Language(models.Model): 46 | language = models.CharField(max_length=100, null=True, blank=True) 47 | language_level = models.CharField(max_length=100, null=True, blank=True) 48 | is_native = models.BooleanField(null=True, blank=True, default=False) 49 | 50 | def __str__(self): 51 | return self.language 52 | 53 | 54 | class Resume(models.Model): 55 | user = models.ForeignKey(User, on_delete=models.CASCADE) 56 | name = models.CharField(max_length=100, null=True, blank=True) 57 | email = models.EmailField(null=True, blank=True) 58 | phone_number = models.CharField(max_length=20, null=True, blank=True) 59 | summary = models.TextField(null=True, blank=True) 60 | work_experience = models.ManyToManyField(Experience, blank=True) 61 | education = models.ManyToManyField(Education, blank=True) 62 | skills = models.ManyToManyField(Skill, blank=True) 63 | projects = models.ManyToManyField(Project, blank=True) 64 | languages = models.ManyToManyField(Language, blank=True) 65 | 66 | def __str__(self): 67 | return self.name 68 | -------------------------------------------------------------------------------- /Phase_3/config/settings/local.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa 2 | from .base import env 3 | 4 | # GENERAL 5 | # ------------------------------------------------------------------------------ 6 | # https://docs.djangoproject.com/en/dev/ref/settings/#debug 7 | DEBUG = True 8 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 9 | SECRET_KEY = env( 10 | "DJANGO_SECRET_KEY", 11 | default="lMFqmWXHUQQ1dKu3y3rImJczkicERj1BPYW3bSx2A0Uen8hmI2GQVgKxRTI0poUd", 12 | ) 13 | # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts 14 | ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] 15 | 16 | # CACHES 17 | # ------------------------------------------------------------------------------ 18 | # https://docs.djangoproject.com/en/dev/ref/settings/#caches 19 | CACHES = { 20 | "default": { 21 | "BACKEND": "django.core.cache.backends.locmem.LocMemCache", 22 | "LOCATION": "", 23 | } 24 | } 25 | 26 | # EMAIL 27 | # ------------------------------------------------------------------------------ 28 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend 29 | EMAIL_BACKEND = env("DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend") 30 | 31 | # WhiteNoise 32 | # ------------------------------------------------------------------------------ 33 | # http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development 34 | INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS # noqa: F405 35 | 36 | 37 | # django-debug-toolbar 38 | # ------------------------------------------------------------------------------ 39 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites 40 | INSTALLED_APPS += ["debug_toolbar"] # noqa: F405 41 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware 42 | MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa: F405 43 | # https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config 44 | DEBUG_TOOLBAR_CONFIG = { 45 | "DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"], 46 | "SHOW_TEMPLATE_CONTEXT": True, 47 | } 48 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips 49 | INTERNAL_IPS = ["127.0.0.1", "10.0.2.2"] 50 | if env("USE_DOCKER") == "yes": 51 | import socket 52 | 53 | hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) 54 | INTERNAL_IPS += [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips] 55 | 56 | # django-extensions 57 | # ------------------------------------------------------------------------------ 58 | # https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration 59 | INSTALLED_APPS += ["django_extensions"] # noqa: F405 60 | 61 | # Your stuff... 62 | # ------------------------------------------------------------------------------ 63 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/resume/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.9 on 2024-01-28 18:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Education", 15 | fields=[ 16 | ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), 17 | ("institution_name", models.CharField(max_length=100)), 18 | ("degree", models.CharField(max_length=100)), 19 | ("start_date", models.DateField()), 20 | ("end_date", models.DateField()), 21 | ("description", models.TextField()), 22 | ], 23 | ), 24 | migrations.CreateModel( 25 | name="Experience", 26 | fields=[ 27 | ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), 28 | ("company_name", models.CharField(max_length=100)), 29 | ("job_title", models.CharField(max_length=100)), 30 | ("start_date", models.DateField()), 31 | ("end_date", models.DateField()), 32 | ("description", models.TextField()), 33 | ], 34 | ), 35 | migrations.CreateModel( 36 | name="Language", 37 | fields=[ 38 | ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), 39 | ("language", models.CharField(max_length=100)), 40 | ("language_level", models.CharField(max_length=100)), 41 | ("is_native", models.BooleanField()), 42 | ], 43 | ), 44 | migrations.CreateModel( 45 | name="Project", 46 | fields=[ 47 | ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), 48 | ("project_name", models.CharField(max_length=100)), 49 | ("project_description", models.TextField()), 50 | ], 51 | ), 52 | migrations.CreateModel( 53 | name="Skill", 54 | fields=[ 55 | ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), 56 | ("skill_name", models.CharField(max_length=100)), 57 | ("skill_description", models.TextField()), 58 | ], 59 | ), 60 | migrations.CreateModel( 61 | name="Resume", 62 | fields=[ 63 | ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), 64 | ("name", models.CharField(max_length=100)), 65 | ("email", models.EmailField(max_length=254)), 66 | ("phone_number", models.CharField(max_length=20)), 67 | ("summary", models.TextField()), 68 | ("education", models.ManyToManyField(to="resume.education")), 69 | ("projects", models.ManyToManyField(to="resume.project")), 70 | ("skills", models.ManyToManyField(to="resume.skill")), 71 | ("work_experience", models.ManyToManyField(to="resume.experience")), 72 | ], 73 | ), 74 | ] 75 | -------------------------------------------------------------------------------- /Phase_3/config/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.static import static 3 | from django.contrib import admin 4 | from django.urls import include, path 5 | from django.views import defaults as default_views 6 | from django.views.generic import TemplateView 7 | from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView 8 | from drf_yasg import openapi 9 | from drf_yasg.views import get_schema_view 10 | from rest_framework import permissions 11 | from rest_framework.authtoken.views import obtain_auth_token 12 | 13 | urlpatterns = [ 14 | path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), 15 | path("about/", TemplateView.as_view(template_name="pages/about.html"), name="about"), 16 | # Django Admin, use {% url 'admin:index' %} 17 | path(settings.ADMIN_URL, admin.site.urls), 18 | # User management 19 | path("users/", include("resume_builder.users.urls", namespace="users")), 20 | path("accounts/", include("allauth.urls")), 21 | # Your stuff: custom urls includes go here 22 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 23 | 24 | # API URLS 25 | urlpatterns += [ 26 | # API base url 27 | path("api/", include("config.api_router")), 28 | # DRF auth token 29 | path("auth-token/", obtain_auth_token), 30 | path("api/schema/", SpectacularAPIView.as_view(), name="api-schema"), 31 | path( 32 | "api/docs/", 33 | SpectacularSwaggerView.as_view(url_name="api-schema"), 34 | name="api-docs", 35 | ), 36 | ] 37 | 38 | if settings.DEBUG: 39 | # This allows the error pages to be debugged during development, just visit 40 | # these url in browser to see how these error pages look like. 41 | urlpatterns += [ 42 | path( 43 | "400/", 44 | default_views.bad_request, 45 | kwargs={"exception": Exception("Bad Request!")}, 46 | ), 47 | path( 48 | "403/", 49 | default_views.permission_denied, 50 | kwargs={"exception": Exception("Permission Denied")}, 51 | ), 52 | path( 53 | "404/", 54 | default_views.page_not_found, 55 | kwargs={"exception": Exception("Page not Found")}, 56 | ), 57 | path("500/", default_views.server_error), 58 | ] 59 | if "debug_toolbar" in settings.INSTALLED_APPS: 60 | import debug_toolbar 61 | 62 | urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns 63 | 64 | schema_view = get_schema_view( 65 | openapi.Info( 66 | title="Snippets API", 67 | default_version="v1", 68 | description="Test description", 69 | terms_of_service="https://www.google.com/policies/terms/", 70 | contact=openapi.Contact(email="contact@snippets.local"), 71 | license=openapi.License(name="BSD License"), 72 | ), 73 | public=True, 74 | permission_classes=(permissions.AllowAny,), 75 | ) 76 | 77 | urlpatterns += [ 78 | path("swagger/", schema_view.without_ui(cache_timeout=0), name="schema-json"), 79 | path("swagger/", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"), 80 | path("redoc/", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"), 81 | ] 82 | 83 | from resume_builder.resume.views import ResumeTemplate 84 | 85 | urlpatterns += [ 86 | path("resume/", ResumeTemplate.as_view(), name="resume"), 87 | ] -------------------------------------------------------------------------------- /Phase_3/resume_builder/templates/account/email.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "account/base.html" %} 3 | 4 | {% load i18n %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %} 8 | {% translate "Account" %} 9 | {% endblock head_title %} 10 | {% block inner %} 11 |

{% translate "E-mail Addresses" %}

12 | {% if user.emailaddress_set.all %} 13 |

{% 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 |
58 | {% csrf_token %} 59 | {{ form|crispy }} 60 | 61 |
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 |
109 | {{ message }} 110 | 114 |
115 | {% endfor %} 116 | {% endif %} 117 | {% block content %} 118 |

Use this document as a way to quick start any new project.

119 | {% endblock content %} 120 |
121 | 122 | {% block modal %} 123 | {% endblock modal %} 124 | {% block inline_javascript %} 125 | {% comment %} 126 | Script tags with only code, no src (defer by default). To run 127 | with a "defer" so that you run inline code: 128 | 133 | {% endcomment %} 134 | {% endblock inline_javascript %} 135 | 136 | 137 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/static/css/main.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #555; 4 | line-height: 1.4em; 5 | padding: 0; 6 | margin: 0; 7 | } 8 | 9 | #left { 10 | background-color: #363636; 11 | line-height: 1em; 12 | } 13 | #left * { 14 | color: #eee; 15 | } 16 | #left h3 , #left h4 { 17 | color: #fff; 18 | } 19 | #left .btn { 20 | color: #fff; 21 | } 22 | #left .btn.btn-default { 23 | color: #666; 24 | } 25 | 26 | 27 | #panel { 28 | padding: 5%; 29 | color: white; 30 | padding-top: 10px; 31 | padding-bottom: 10px; 32 | } 33 | #titleBox * { 34 | font-size: 2em; 35 | } 36 | #customTemplateOptions { 37 | display: none; 38 | } 39 | 40 | 41 | 42 | 43 | #page { 44 | /* width: 794px; */ 45 | /* height: 29.7cm; */ 46 | width: 21cm; 47 | background-color: white; 48 | margin: 1% auto 1% auto; 49 | padding: 0.2cm 1.3cm 1cm 1.3cm; 50 | box-shadow: 0px 0px 20px 0px #000; 51 | line-height: 1.4em; 52 | text-align: left; 53 | } 54 | 55 | 56 | #page h2 { 57 | margin-bottom: 4px; 58 | } 59 | #page h5 { 60 | margin-top: 2px; margin-bottom: 3px; 61 | } 62 | 63 | a { 64 | color: #666; 65 | } 66 | a.blue { 67 | color: blue; 68 | text-decoration: none; 69 | border-bottom: 1px dotted blue; 70 | } 71 | a.blue:hover { 72 | color: blue; 73 | text-decoration: none; 74 | border-bottom: 1px solid blue; 75 | } 76 | 77 | ul { 78 | margin-left: 20px; 79 | padding-left: 20px; 80 | } 81 | li { 82 | margin-top: 1%; 83 | } 84 | li li { 85 | margin-top: 0.25%; 86 | } 87 | 88 | 89 | 90 | .star { 91 | margin-top: 5px; 92 | } 93 | .light { 94 | font-weight: 300; 95 | } 96 | .grey { 97 | color: #666; 98 | } 99 | .borderless { margin-bottom: 0; } 100 | .half { 101 | display: inline-block; 102 | width: 49.5%; 103 | } 104 | .left { 105 | float: left; 106 | } 107 | .right { 108 | float: right; 109 | } 110 | .tab { 111 | margin-left: 10px; 112 | } 113 | 114 | 115 | 116 | #image_box { 117 | float: left; 118 | margin-top: 25px; 119 | margin-left: 5px; 120 | margin-right: 15px; 121 | } 122 | #info { 123 | margin-bottom: 0px; 124 | margin-top: auto; 125 | } 126 | #contact { 127 | position: absolute; 128 | bottom: 0; 129 | right: 20px; 130 | text-align: right; 131 | } 132 | #contentMinor , #contentBranch , #contactLink2 { 133 | display: none; 134 | } 135 | 136 | 137 | .table tbody tr td, .table tbody tr th, .table thead tr th { 138 | border: none; 139 | padding: 0; 140 | font-size: 13.5px; 141 | } 142 | td { 143 | color: #444; 144 | } 145 | td.header { 146 | color: #333; 147 | } 148 | .table.customBordered , .table.customBordered * { 149 | border: 0px; 150 | } 151 | .table.customBordered { 152 | border-top: 1px solid #999; 153 | border-left: 1px solid #999; 154 | } 155 | .table.customBordered tr { 156 | border-bottom: 1px solid #999; 157 | } 158 | .table.customBordered tr td { 159 | padding: 0px 5px 0px 5px; 160 | border-right: 1px solid #999; 161 | } 162 | 163 | 164 | .section-title h4 { 165 | margin-top: 15px; 166 | margin-bottom: 0px; 167 | } 168 | .section-title.uppercase h4 { 169 | text-transform: uppercase; 170 | } 171 | .section-title.shaded h4 { 172 | padding: 3px; 173 | padding-left: 10px; 174 | font-size: 14px; 175 | background-color: #ccc !important; 176 | } 177 | 178 | .section-title.ruled.rule-above hr.hr-above { 179 | border: 1px solid hsl(0,0%,40%); 180 | margin-top: 0px; margin-bottom: 0px; 181 | } 182 | .section-title.ruled.rule-above hr.hr-below { 183 | border: none; 184 | margin: 0px; 185 | } 186 | .section-title.ruled.rule-below hr.hr-above { 187 | border: none; 188 | margin: 0px; 189 | } 190 | .section-title.ruled.rule-below hr.hr-below { 191 | border: 1px solid hsl(0,0%,40%); 192 | margin-top: 2px; margin-bottom: 5px; 193 | } 194 | .section-title.shaded hr { 195 | border: none; 196 | margin: 0px; 197 | } 198 | 199 | 200 | .text { 201 | color: #444; 202 | font-size: 13.5px; 203 | } 204 | .time { 205 | color: #333; 206 | font-style: italic; 207 | margin-right: 1px; 208 | } 209 | .mentor { 210 | font-weight: 300; 211 | font-style: italic; 212 | } 213 | .link { 214 | color: #666; 215 | margin-left: 30px; 216 | font-weight: 300; 217 | font-style: italic; 218 | margin-right: 1px; 219 | } 220 | 221 | .no-link .link { 222 | display: none; 223 | } 224 | 225 | #sectionProjects .mentor , #sectionProjects .link { 226 | display: inline-block; 227 | } 228 | #sectionSkills li , #sectionCourses li { 229 | margin-top: 0.5%; 230 | } 231 | #sectionExperience .title , #sectionExperience .time { 232 | display: inline-block; 233 | } 234 | #sectionPublications .title , #sectionPublications .time { 235 | display: inline-block; 236 | } 237 | #sectionProjects .title , #sectionProjects .time { 238 | display: inline-block; 239 | } 240 | #sectionInterest { 241 | display: none; 242 | } 243 | #sectionFooterMessage { 244 | display: none; 245 | } 246 | 247 | 248 | 249 | #page.verdana-sans * { 250 | font-family: "Verdana", sans-serif; 251 | } 252 | #page.verdana-serif * { 253 | font-family: "Verdana", serif; 254 | } 255 | #page.roboto * { 256 | font-family: 'Roboto', sans-serif; 257 | } 258 | #page.droid * { 259 | font-family: 'Droid Serif', serif; 260 | } 261 | .droid strong , .verdana-sans strong , .verdana-serif strong { 262 | font-weight: 700; 263 | } 264 | .roboto strong { 265 | font-weight: 500; 266 | } 267 | .droid .title , .verdana-sans .title , .verdana-serif .title { 268 | font-weight: 700; 269 | } 270 | .roboto .title { 271 | font-weight: 500; 272 | } 273 | 274 | #notice { 275 | background-color: #C0392B; 276 | border-radius: 5px; 277 | padding: 10px; 278 | margin-bottom: 10px; 279 | color: white; 280 | text-align: center; 281 | } 282 | #notice a { 283 | color: #F1C40F; 284 | } -------------------------------------------------------------------------------- /Phase_3/resume_builder/resume/migrations/0002_resume_user_alter_education_degree_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.9 on 2024-01-28 19:02 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("resume", "0001_initial"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name="resume", 18 | name="user", 19 | field=models.ForeignKey( 20 | default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL 21 | ), 22 | preserve_default=False, 23 | ), 24 | migrations.AlterField( 25 | model_name="education", 26 | name="degree", 27 | field=models.CharField(blank=True, max_length=100, null=True), 28 | ), 29 | migrations.AlterField( 30 | model_name="education", 31 | name="description", 32 | field=models.TextField(blank=True, null=True), 33 | ), 34 | migrations.AlterField( 35 | model_name="education", 36 | name="end_date", 37 | field=models.DateField(blank=True, null=True), 38 | ), 39 | migrations.AlterField( 40 | model_name="education", 41 | name="institution_name", 42 | field=models.CharField(blank=True, max_length=100, null=True), 43 | ), 44 | migrations.AlterField( 45 | model_name="education", 46 | name="start_date", 47 | field=models.DateField(blank=True, null=True), 48 | ), 49 | migrations.AlterField( 50 | model_name="experience", 51 | name="company_name", 52 | field=models.CharField(blank=True, max_length=100, null=True), 53 | ), 54 | migrations.AlterField( 55 | model_name="experience", 56 | name="description", 57 | field=models.TextField(blank=True, null=True), 58 | ), 59 | migrations.AlterField( 60 | model_name="experience", 61 | name="end_date", 62 | field=models.DateField(blank=True, null=True), 63 | ), 64 | migrations.AlterField( 65 | model_name="experience", 66 | name="job_title", 67 | field=models.CharField(blank=True, max_length=100, null=True), 68 | ), 69 | migrations.AlterField( 70 | model_name="experience", 71 | name="start_date", 72 | field=models.DateField(blank=True, null=True), 73 | ), 74 | migrations.AlterField( 75 | model_name="language", 76 | name="is_native", 77 | field=models.BooleanField(blank=True, default=False, null=True), 78 | ), 79 | migrations.AlterField( 80 | model_name="language", 81 | name="language", 82 | field=models.CharField(blank=True, max_length=100, null=True), 83 | ), 84 | migrations.AlterField( 85 | model_name="language", 86 | name="language_level", 87 | field=models.CharField(blank=True, max_length=100, null=True), 88 | ), 89 | migrations.AlterField( 90 | model_name="project", 91 | name="project_description", 92 | field=models.TextField(blank=True, null=True), 93 | ), 94 | migrations.AlterField( 95 | model_name="project", 96 | name="project_name", 97 | field=models.CharField(blank=True, max_length=100, null=True), 98 | ), 99 | migrations.AlterField( 100 | model_name="resume", 101 | name="education", 102 | field=models.ManyToManyField(blank=True, to="resume.education"), 103 | ), 104 | migrations.AlterField( 105 | model_name="resume", 106 | name="email", 107 | field=models.EmailField(blank=True, max_length=254, null=True), 108 | ), 109 | migrations.AlterField( 110 | model_name="resume", 111 | name="name", 112 | field=models.CharField(blank=True, max_length=100, null=True), 113 | ), 114 | migrations.AlterField( 115 | model_name="resume", 116 | name="phone_number", 117 | field=models.CharField(blank=True, max_length=20, null=True), 118 | ), 119 | migrations.AlterField( 120 | model_name="resume", 121 | name="projects", 122 | field=models.ManyToManyField(blank=True, to="resume.project"), 123 | ), 124 | migrations.AlterField( 125 | model_name="resume", 126 | name="skills", 127 | field=models.ManyToManyField(blank=True, to="resume.skill"), 128 | ), 129 | migrations.AlterField( 130 | model_name="resume", 131 | name="summary", 132 | field=models.TextField(blank=True, null=True), 133 | ), 134 | migrations.AlterField( 135 | model_name="resume", 136 | name="work_experience", 137 | field=models.ManyToManyField(blank=True, to="resume.experience"), 138 | ), 139 | migrations.AlterField( 140 | model_name="skill", 141 | name="skill_description", 142 | field=models.TextField(blank=True, null=True), 143 | ), 144 | migrations.AlterField( 145 | model_name="skill", 146 | name="skill_name", 147 | field=models.CharField(blank=True, max_length=100, null=True), 148 | ), 149 | ] 150 | -------------------------------------------------------------------------------- /Phase_3/config/settings/production.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa 2 | from .base import env 3 | 4 | # GENERAL 5 | # ------------------------------------------------------------------------------ 6 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 7 | SECRET_KEY = env("DJANGO_SECRET_KEY") 8 | # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts 9 | ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["example.com"]) 10 | 11 | # DATABASES 12 | # ------------------------------------------------------------------------------ 13 | DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa: F405 14 | 15 | # CACHES 16 | # ------------------------------------------------------------------------------ 17 | CACHES = { 18 | "default": { 19 | "BACKEND": "django_redis.cache.RedisCache", 20 | "LOCATION": env("REDIS_URL"), 21 | "OPTIONS": { 22 | "CLIENT_CLASS": "django_redis.client.DefaultClient", 23 | # Mimicing memcache behavior. 24 | # https://github.com/jazzband/django-redis#memcached-exceptions-behavior 25 | "IGNORE_EXCEPTIONS": True, 26 | }, 27 | } 28 | } 29 | 30 | # SECURITY 31 | # ------------------------------------------------------------------------------ 32 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header 33 | SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") 34 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect 35 | SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True) 36 | # https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure 37 | SESSION_COOKIE_SECURE = True 38 | # https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure 39 | CSRF_COOKIE_SECURE = True 40 | # https://docs.djangoproject.com/en/dev/topics/security/#ssl-https 41 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds 42 | # TODO: set this to 60 seconds first and then to 518400 once you prove the former works 43 | SECURE_HSTS_SECONDS = 60 44 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains 45 | SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool("DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True) 46 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload 47 | SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True) 48 | # https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff 49 | SECURE_CONTENT_TYPE_NOSNIFF = env.bool("DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True) 50 | 51 | # STATIC & MEDIA 52 | # ------------------------ 53 | STORAGES = { 54 | "default": { 55 | "BACKEND": "django.core.files.storage.FileSystemStorage", 56 | }, 57 | "staticfiles": { 58 | "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", 59 | }, 60 | } 61 | 62 | # EMAIL 63 | # ------------------------------------------------------------------------------ 64 | # https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email 65 | DEFAULT_FROM_EMAIL = env( 66 | "DJANGO_DEFAULT_FROM_EMAIL", 67 | default="Resume Builder ", 68 | ) 69 | # https://docs.djangoproject.com/en/dev/ref/settings/#server-email 70 | SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL) 71 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix 72 | EMAIL_SUBJECT_PREFIX = env( 73 | "DJANGO_EMAIL_SUBJECT_PREFIX", 74 | default="[Resume Builder] ", 75 | ) 76 | 77 | # ADMIN 78 | # ------------------------------------------------------------------------------ 79 | # Django Admin URL regex. 80 | ADMIN_URL = env("DJANGO_ADMIN_URL") 81 | 82 | # Anymail 83 | # ------------------------------------------------------------------------------ 84 | # https://anymail.readthedocs.io/en/stable/installation/#installing-anymail 85 | INSTALLED_APPS += ["anymail"] # noqa: F405 86 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend 87 | # https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference 88 | # https://anymail.readthedocs.io/en/stable/esps/mailgun/ 89 | EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend" 90 | ANYMAIL = { 91 | "MAILGUN_API_KEY": env("MAILGUN_API_KEY"), 92 | "MAILGUN_SENDER_DOMAIN": env("MAILGUN_DOMAIN"), 93 | "MAILGUN_API_URL": env("MAILGUN_API_URL", default="https://api.mailgun.net/v3"), 94 | } 95 | 96 | 97 | # LOGGING 98 | # ------------------------------------------------------------------------------ 99 | # https://docs.djangoproject.com/en/dev/ref/settings/#logging 100 | # See https://docs.djangoproject.com/en/dev/topics/logging for 101 | # more details on how to customize your logging configuration. 102 | # A sample logging configuration. The only tangible logging 103 | # performed by this configuration is to send an email to 104 | # the site admins on every HTTP 500 error when DEBUG=False. 105 | LOGGING = { 106 | "version": 1, 107 | "disable_existing_loggers": False, 108 | "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, 109 | "formatters": { 110 | "verbose": { 111 | "format": "%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s", 112 | }, 113 | }, 114 | "handlers": { 115 | "mail_admins": { 116 | "level": "ERROR", 117 | "filters": ["require_debug_false"], 118 | "class": "django.utils.log.AdminEmailHandler", 119 | }, 120 | "console": { 121 | "level": "DEBUG", 122 | "class": "logging.StreamHandler", 123 | "formatter": "verbose", 124 | }, 125 | }, 126 | "root": {"level": "INFO", "handlers": ["console"]}, 127 | "loggers": { 128 | "django.request": { 129 | "handlers": ["mail_admins"], 130 | "level": "ERROR", 131 | "propagate": True, 132 | }, 133 | "django.security.DisallowedHost": { 134 | "level": "ERROR", 135 | "handlers": ["console", "mail_admins"], 136 | "propagate": True, 137 | }, 138 | }, 139 | } 140 | 141 | # django-rest-framework 142 | # ------------------------------------------------------------------------------- 143 | # Tools that generate code samples can use SERVERS to point to the correct domain 144 | SPECTACULAR_SETTINGS["SERVERS"] = [ # noqa: F405 145 | {"url": "https://example.com", "description": "Production server"}, 146 | ] 147 | # Your stuff... 148 | # ------------------------------------------------------------------------------ 149 | -------------------------------------------------------------------------------- /Phase_3/resume_builder/static/js/main.js: -------------------------------------------------------------------------------- 1 | document.querySelector('#page').contentEditable = true; 2 | 3 | defaultTemplateVars = [ "fontDroid" , "caseNormal" , "titleRuled" , "ruleAbove" , "imageShow" , "rollShow" , "course1" , "tableShow" , "edyearFirst" , "experience1" , "projects1" ] 4 | 5 | $('.toggle-option').click(function(){ 6 | toggleType = $(this).attr('data-toggle'); 7 | toggleValue = $(this).attr('id'); 8 | if(!$(this).hasClass('multi-select')) 9 | { 10 | if(!$(this).hasClass('selected')) 11 | { 12 | $('.toggle-option',$(this).parent()).removeClass('selected'); 13 | $(this).addClass('selected'); 14 | changeTemplate(toggleType,toggleValue); 15 | } 16 | } 17 | else 18 | { 19 | if($(this).hasClass('selected')) 20 | $(this).removeClass('selected'); 21 | else 22 | $(this).addClass('selected'); 23 | changeTemplate(toggleType,toggleValue); 24 | } 25 | }); 26 | 27 | $('input[name="sectionToggle"]').change(function(){ 28 | toggleSection($(this).val(),$(this).is(':checked')); 29 | }); 30 | 31 | 32 | function template(value) 33 | { 34 | if(value=='default') 35 | { 36 | $('#defaultTemplateBtn').removeClass('btn-default').addClass('btn-danger'); 37 | $('#customTemplateBtn').removeClass('btn-danger').addClass('btn-default'); 38 | $('#customTemplateOptions').hide(); 39 | for(i=0;i 0) 325 | range = sel.getRangeAt(0); 326 | } 327 | else 328 | { 329 | // Old WebKit selection object has no getRangeAt, so 330 | // create a range from other selection properties 331 | range = document.createRange(); 332 | range.setStart(sel.anchorNode, sel.anchorOffset); 333 | range.setEnd(sel.focusNode, sel.focusOffset); 334 | // Handle the case when the selection was selected backwards (from the end to the start in the document) 335 | if (range.collapsed !== sel.isCollapsed) 336 | { 337 | range.setStart(sel.focusNode, sel.focusOffset); 338 | range.setEnd(sel.anchorNode, sel.anchorOffset); 339 | } 340 | } 341 | if (range) 342 | { 343 | container = range.commonAncestorContainer; 344 | // Check if the container is a text node and return its parent if so 345 | return container.nodeType === 3 ? container.parentNode : container; 346 | } 347 | } 348 | } 349 | 350 | function insertAfter(referenceNode,newNode) { 351 | referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); 352 | } -------------------------------------------------------------------------------- /Phase_3/locale/pt_BR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # Translations for the Resume Builder project 2 | # Copyright (C) 2024 Alireza Parvaresh 3 | # Alireza Parvaresh , 2024. 4 | # 5 | #, fuzzy 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: 0.1.0\n" 9 | "Language: pt-BR\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 14 | #: resume_builder/templates/account/account_inactive.html:5 15 | #: resume_builder/templates/account/account_inactive.html:8 16 | msgid "Account Inactive" 17 | msgstr "Conta Inativa" 18 | 19 | #: resume_builder/templates/account/account_inactive.html:10 20 | msgid "This account is inactive." 21 | msgstr "Esta conta está inativa." 22 | 23 | #: resume_builder/templates/account/email.html:7 24 | msgid "Account" 25 | msgstr "Conta" 26 | 27 | #: resume_builder/templates/account/email.html:10 28 | msgid "E-mail Addresses" 29 | msgstr "Endereços de E-mail" 30 | 31 | #: resume_builder/templates/account/email.html:13 32 | msgid "The following e-mail addresses are associated with your account:" 33 | msgstr "Os seguintes endereços de e-mail estão associados à sua conta:" 34 | 35 | #: resume_builder/templates/account/email.html:27 36 | msgid "Verified" 37 | msgstr "Verificado" 38 | 39 | #: resume_builder/templates/account/email.html:29 40 | msgid "Unverified" 41 | msgstr "Não verificado" 42 | 43 | #: resume_builder/templates/account/email.html:31 44 | msgid "Primary" 45 | msgstr "Primário" 46 | 47 | #: resume_builder/templates/account/email.html:37 48 | msgid "Make Primary" 49 | msgstr "Tornar Primário" 50 | 51 | #: resume_builder/templates/account/email.html:38 52 | msgid "Re-send Verification" 53 | msgstr "Reenviar verificação" 54 | 55 | #: resume_builder/templates/account/email.html:39 56 | msgid "Remove" 57 | msgstr "Remover" 58 | 59 | #: resume_builder/templates/account/email.html:46 60 | msgid "Warning:" 61 | msgstr "Aviso:" 62 | 63 | #: resume_builder/templates/account/email.html:46 64 | msgid "" 65 | "You currently do not have any e-mail address set up. You should really add " 66 | "an e-mail address so you can receive notifications, reset your password, etc." 67 | msgstr "" 68 | "No momento, você não tem nenhum endereço de e-mail configurado. Você " 69 | "realmente deve adicionar um endereço de e-mail para receber notificações, " 70 | "redefinir sua senha etc." 71 | 72 | #: resume_builder/templates/account/email.html:51 73 | msgid "Add E-mail Address" 74 | msgstr "Adicionar Endereço de E-mail" 75 | 76 | #: resume_builder/templates/account/email.html:56 77 | msgid "Add E-mail" 78 | msgstr "Adicionar E-mail" 79 | 80 | #: resume_builder/templates/account/email.html:66 81 | msgid "Do you really want to remove the selected e-mail address?" 82 | msgstr "Você realmente deseja remover o endereço de e-mail selecionado?" 83 | 84 | #: resume_builder/templates/account/email_confirm.html:6 85 | #: resume_builder/templates/account/email_confirm.html:10 86 | msgid "Confirm E-mail Address" 87 | msgstr "Confirme o endereço de e-mail" 88 | 89 | #: resume_builder/templates/account/email_confirm.html:16 90 | #, python-format 91 | msgid "" 92 | "Please confirm that %(email)s is an e-mail " 93 | "address for user %(user_display)s." 94 | msgstr "" 95 | "Confirme se %(email)s é um endereço de " 96 | "e-mail do usuário %(user_display)s." 97 | 98 | #: resume_builder/templates/account/email_confirm.html:20 99 | msgid "Confirm" 100 | msgstr "Confirmar" 101 | 102 | #: resume_builder/templates/account/email_confirm.html:27 103 | #, python-format 104 | msgid "" 105 | "This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request." 107 | msgstr "Este link de confirmação de e-mail expirou ou é inválido. " 108 | "Por favor, emita um novo pedido de confirmação por e-mail." 109 | 110 | #: resume_builder/templates/account/login.html:7 111 | #: resume_builder/templates/account/login.html:11 112 | #: resume_builder/templates/account/login.html:56 113 | #: resume_builder/templates/base.html:72 114 | msgid "Sign In" 115 | msgstr "Entrar" 116 | 117 | #: resume_builder/templates/account/login.html:17 118 | msgid "Please sign in with one of your existing third party accounts:" 119 | msgstr "Faça login com uma de suas contas de terceiros existentes:" 120 | 121 | #: resume_builder/templates/account/login.html:19 122 | #, python-format 123 | msgid "" 124 | "Or, sign up for a %(site_name)s account and " 125 | "sign in below:" 126 | msgstr "Ou, cadastre-se para uma conta em %(site_name)s e entre abaixo:" 127 | 128 | #: resume_builder/templates/account/login.html:32 129 | msgid "or" 130 | msgstr "ou" 131 | 132 | #: resume_builder/templates/account/login.html:41 133 | #, python-format 134 | msgid "" 135 | "If you have not created an account yet, then please sign up first." 137 | msgstr "Se você ainda não criou uma conta, registre-se primeiro." 139 | 140 | #: resume_builder/templates/account/login.html:55 141 | msgid "Forgot Password?" 142 | msgstr "Esqueceu sua senha?" 143 | 144 | #: resume_builder/templates/account/logout.html:5 145 | #: resume_builder/templates/account/logout.html:8 146 | #: resume_builder/templates/account/logout.html:17 147 | #: resume_builder/templates/base.html:61 148 | msgid "Sign Out" 149 | msgstr "Sair" 150 | 151 | #: resume_builder/templates/account/logout.html:10 152 | msgid "Are you sure you want to sign out?" 153 | msgstr "Você tem certeza que deseja sair?" 154 | 155 | #: resume_builder/templates/account/password_change.html:6 156 | #: resume_builder/templates/account/password_change.html:9 157 | #: resume_builder/templates/account/password_change.html:14 158 | #: resume_builder/templates/account/password_reset_from_key.html:5 159 | #: resume_builder/templates/account/password_reset_from_key.html:8 160 | #: resume_builder/templates/account/password_reset_from_key_done.html:4 161 | #: resume_builder/templates/account/password_reset_from_key_done.html:7 162 | msgid "Change Password" 163 | msgstr "Alterar Senha" 164 | 165 | #: resume_builder/templates/account/password_reset.html:7 166 | #: resume_builder/templates/account/password_reset.html:11 167 | #: resume_builder/templates/account/password_reset_done.html:6 168 | #: resume_builder/templates/account/password_reset_done.html:9 169 | msgid "Password Reset" 170 | msgstr "Redefinição de senha" 171 | 172 | #: resume_builder/templates/account/password_reset.html:16 173 | msgid "" 174 | "Forgotten your password? Enter your e-mail address below, and we'll send you " 175 | "an e-mail allowing you to reset it." 176 | msgstr "Esqueceu sua senha? Digite seu endereço de e-mail abaixo e enviaremos um e-mail permitindo que você o redefina." 177 | 178 | #: resume_builder/templates/account/password_reset.html:21 179 | msgid "Reset My Password" 180 | msgstr "Redefinir minha senha" 181 | 182 | #: resume_builder/templates/account/password_reset.html:24 183 | msgid "Please contact us if you have any trouble resetting your password." 184 | msgstr "Entre em contato conosco se tiver algum problema para redefinir sua senha." 185 | 186 | #: resume_builder/templates/account/password_reset_done.html:15 187 | msgid "" 188 | "We have sent you an e-mail. Please contact us if you do not receive it " 189 | "within a few minutes." 190 | msgstr "Enviamos um e-mail para você. Entre em contato conosco se você não recebê-lo dentro de alguns minutos." 191 | 192 | #: resume_builder/templates/account/password_reset_from_key.html:8 193 | msgid "Bad Token" 194 | msgstr "Token Inválido" 195 | 196 | #: resume_builder/templates/account/password_reset_from_key.html:12 197 | #, python-format 198 | msgid "" 199 | "The password reset link was invalid, possibly because it has already been " 200 | "used. Please request a new password reset." 202 | msgstr "O link de redefinição de senha era inválido, possivelmente porque já foi usado. " 203 | "Solicite uma nova redefinição de senha." 204 | 205 | #: resume_builder/templates/account/password_reset_from_key.html:18 206 | msgid "change password" 207 | msgstr "alterar senha" 208 | 209 | #: resume_builder/templates/account/password_reset_from_key.html:21 210 | #: resume_builder/templates/account/password_reset_from_key_done.html:8 211 | msgid "Your password is now changed." 212 | msgstr "Sua senha agora foi alterada." 213 | 214 | #: resume_builder/templates/account/password_set.html:6 215 | #: resume_builder/templates/account/password_set.html:9 216 | #: resume_builder/templates/account/password_set.html:14 217 | msgid "Set Password" 218 | msgstr "Definir Senha" 219 | 220 | #: resume_builder/templates/account/signup.html:6 221 | msgid "Signup" 222 | msgstr "Cadastro" 223 | 224 | #: resume_builder/templates/account/signup.html:9 225 | #: resume_builder/templates/account/signup.html:19 226 | #: resume_builder/templates/base.html:67 227 | msgid "Sign Up" 228 | msgstr "Cadastro" 229 | 230 | #: resume_builder/templates/account/signup.html:11 231 | #, python-format 232 | msgid "" 233 | "Already have an account? Then please sign in." 234 | msgstr "já tem uma conta? Então, por favor, faça login." 235 | 236 | #: resume_builder/templates/account/signup_closed.html:5 237 | #: resume_builder/templates/account/signup_closed.html:8 238 | msgid "Sign Up Closed" 239 | msgstr "Inscrições encerradas" 240 | 241 | #: resume_builder/templates/account/signup_closed.html:10 242 | msgid "We are sorry, but the sign up is currently closed." 243 | msgstr "Lamentamos, mas as inscrições estão encerradas no momento." 244 | 245 | #: resume_builder/templates/account/verification_sent.html:5 246 | #: resume_builder/templates/account/verification_sent.html:8 247 | #: resume_builder/templates/account/verified_email_required.html:5 248 | #: resume_builder/templates/account/verified_email_required.html:8 249 | msgid "Verify Your E-mail Address" 250 | msgstr "Verifique seu endereço de e-mail" 251 | 252 | #: resume_builder/templates/account/verification_sent.html:10 253 | msgid "" 254 | "We have sent an e-mail to you for verification. Follow the link provided to " 255 | "finalize the signup process. Please contact us if you do not receive it " 256 | "within a few minutes." 257 | msgstr "Enviamos um e-mail para você para verificação. Siga o link fornecido para finalizar o processo de inscrição. Entre em contato conosco se você não recebê-lo dentro de alguns minutos." 258 | 259 | #: resume_builder/templates/account/verified_email_required.html:12 260 | msgid "" 261 | "This part of the site requires us to verify that\n" 262 | "you are who you claim to be. For this purpose, we require that you\n" 263 | "verify ownership of your e-mail address. " 264 | msgstr "Esta parte do site exige que verifiquemos se você é quem afirma ser.\n" 265 | "Para esse fim, exigimos que você verifique a propriedade\n" 266 | "do seu endereço de e-mail." 267 | 268 | #: resume_builder/templates/account/verified_email_required.html:16 269 | msgid "" 270 | "We have sent an e-mail to you for\n" 271 | "verification. Please click on the link inside this e-mail. Please\n" 272 | "contact us if you do not receive it within a few minutes." 273 | msgstr "Enviamos um e-mail para você para verificação.\n" 274 | "Por favor, clique no link dentro deste e-mail.\n" 275 | "Entre em contato conosco se você não recebê-lo dentro de alguns minutos." 276 | 277 | #: resume_builder/templates/account/verified_email_required.html:20 278 | #, python-format 279 | msgid "" 280 | "Note: you can still change your e-" 281 | "mail address." 282 | msgstr "Nota: você ainda pode alterar seu endereço de e-mail." 283 | 284 | #: resume_builder/templates/base.html:57 285 | msgid "My Profile" 286 | msgstr "Meu perfil" 287 | 288 | #: resume_builder/users/admin.py:17 289 | msgid "Personal info" 290 | msgstr "Informação pessoal" 291 | 292 | #: resume_builder/users/admin.py:19 293 | msgid "Permissions" 294 | msgstr "Permissões" 295 | 296 | #: resume_builder/users/admin.py:30 297 | msgid "Important dates" 298 | msgstr "Datas importantes" 299 | 300 | #: resume_builder/users/apps.py:7 301 | msgid "Users" 302 | msgstr "Usuários" 303 | 304 | #: resume_builder/users/forms.py:24 305 | #: resume_builder/users/tests/test_forms.py:36 306 | msgid "This username has already been taken." 307 | msgstr "Este nome de usuário já foi usado." 308 | 309 | #: resume_builder/users/models.py:15 310 | msgid "Name of User" 311 | msgstr "Nome do Usuário" 312 | 313 | #: resume_builder/users/views.py:23 314 | msgid "Information successfully updated" 315 | msgstr "Informação atualizada com sucesso" 316 | -------------------------------------------------------------------------------- /Phase_3/locale/fr_FR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # Translations for the Resume Builder project 2 | # Copyright (C) 2024 Alireza Parvaresh 3 | # Alireza Parvaresh , 2024. 4 | # 5 | #, fuzzy 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: 0.1.0\n" 9 | "Language: fr-FR\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 14 | #: resume_builder/templates/account/account_inactive.html:5 15 | #: resume_builder/templates/account/account_inactive.html:8 16 | msgid "Account Inactive" 17 | msgstr "Compte inactif" 18 | 19 | #: resume_builder/templates/account/account_inactive.html:10 20 | msgid "This account is inactive." 21 | msgstr "Ce compte est inactif." 22 | 23 | #: resume_builder/templates/account/email.html:7 24 | msgid "Account" 25 | msgstr "Compte" 26 | 27 | #: resume_builder/templates/account/email.html:10 28 | msgid "E-mail Addresses" 29 | msgstr "Adresses e-mail" 30 | 31 | #: resume_builder/templates/account/email.html:13 32 | msgid "The following e-mail addresses are associated with your account:" 33 | msgstr "Les adresses e-mail suivantes sont associées à votre compte :" 34 | 35 | #: resume_builder/templates/account/email.html:27 36 | msgid "Verified" 37 | msgstr "Vérifié" 38 | 39 | #: resume_builder/templates/account/email.html:29 40 | msgid "Unverified" 41 | msgstr "Non vérifié" 42 | 43 | #: resume_builder/templates/account/email.html:31 44 | msgid "Primary" 45 | msgstr "Primaire" 46 | 47 | #: resume_builder/templates/account/email.html:37 48 | msgid "Make Primary" 49 | msgstr "Changer Primaire" 50 | 51 | #: resume_builder/templates/account/email.html:38 52 | msgid "Re-send Verification" 53 | msgstr "Renvoyer vérification" 54 | 55 | #: resume_builder/templates/account/email.html:39 56 | msgid "Remove" 57 | msgstr "Supprimer" 58 | 59 | #: resume_builder/templates/account/email.html:46 60 | msgid "Warning:" 61 | msgstr "Avertissement:" 62 | 63 | #: resume_builder/templates/account/email.html:46 64 | msgid "" 65 | "You currently do not have any e-mail address set up. You should really add " 66 | "an e-mail address so you can receive notifications, reset your password, etc." 67 | msgstr "" 68 | "Vous n'avez actuellement aucune adresse e-mail configurée. Vous devriez ajouter " 69 | "une adresse e-mail pour reçevoir des notifications, réinitialiser votre mot " 70 | "de passe, etc." 71 | 72 | #: resume_builder/templates/account/email.html:51 73 | msgid "Add E-mail Address" 74 | msgstr "Ajouter une adresse e-mail" 75 | 76 | #: resume_builder/templates/account/email.html:56 77 | msgid "Add E-mail" 78 | msgstr "Ajouter e-mail" 79 | 80 | #: resume_builder/templates/account/email.html:66 81 | msgid "Do you really want to remove the selected e-mail address?" 82 | msgstr "Voulez-vous vraiment supprimer l'adresse e-mail sélectionnée ?" 83 | 84 | #: resume_builder/templates/account/email_confirm.html:6 85 | #: resume_builder/templates/account/email_confirm.html:10 86 | msgid "Confirm E-mail Address" 87 | msgstr "Confirmez votre adresse email" 88 | 89 | #: resume_builder/templates/account/email_confirm.html:16 90 | #, python-format 91 | msgid "" 92 | "Please confirm that %(email)s is an e-mail " 93 | "address for user %(user_display)s." 94 | msgstr "" 95 | "Veuillez confirmer que %(email)s est un e-mail " 96 | "adresse de l'utilisateur %(user_display)s." 97 | 98 | #: resume_builder/templates/account/email_confirm.html:20 99 | msgid "Confirm" 100 | msgstr "Confirm" 101 | 102 | #: resume_builder/templates/account/email_confirm.html:27 103 | #, python-format 104 | msgid "" 105 | "This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request." 107 | msgstr "" 108 | "Ce lien de confirmation par e-mail a expiré ou n'est pas valide. Veuillez" 109 | "émettre une nouvelle demande de confirmation " 110 | "par e-mail." 111 | 112 | #: resume_builder/templates/account/login.html:7 113 | #: resume_builder/templates/account/login.html:11 114 | #: resume_builder/templates/account/login.html:56 115 | #: resume_builder/templates/base.html:72 116 | msgid "Sign In" 117 | msgstr "S'identifier" 118 | 119 | #: resume_builder/templates/account/login.html:17 120 | msgid "Please sign in with one of your existing third party accounts:" 121 | msgstr "Veuillez vous connecter avec l'un de vos comptes tiers existants :" 122 | 123 | #: resume_builder/templates/account/login.html:19 124 | #, python-format 125 | msgid "" 126 | "Or, sign up for a %(site_name)s account and " 127 | "sign in below:" 128 | msgstr "" 129 | "Ou, créez un compte %(site_name)s et " 130 | "connectez-vous ci-dessous :" 131 | 132 | #: resume_builder/templates/account/login.html:32 133 | msgid "or" 134 | msgstr "ou" 135 | 136 | #: resume_builder/templates/account/login.html:41 137 | #, python-format 138 | msgid "" 139 | "If you have not created an account yet, then please sign up first." 141 | msgstr "" 142 | "Si vous n'avez pas encore créé de compte, veuillez d'abord vous inscrire." 144 | 145 | #: resume_builder/templates/account/login.html:55 146 | msgid "Forgot Password?" 147 | msgstr "Mot de passe oublié?" 148 | 149 | #: resume_builder/templates/account/logout.html:5 150 | #: resume_builder/templates/account/logout.html:8 151 | #: resume_builder/templates/account/logout.html:17 152 | #: resume_builder/templates/base.html:61 153 | msgid "Sign Out" 154 | msgstr "Se déconnecter" 155 | 156 | #: resume_builder/templates/account/logout.html:10 157 | msgid "Are you sure you want to sign out?" 158 | msgstr "Êtes-vous certain de vouloir vous déconnecter?" 159 | 160 | #: resume_builder/templates/account/password_change.html:6 161 | #: resume_builder/templates/account/password_change.html:9 162 | #: resume_builder/templates/account/password_change.html:14 163 | #: resume_builder/templates/account/password_reset_from_key.html:5 164 | #: resume_builder/templates/account/password_reset_from_key.html:8 165 | #: resume_builder/templates/account/password_reset_from_key_done.html:4 166 | #: resume_builder/templates/account/password_reset_from_key_done.html:7 167 | msgid "Change Password" 168 | msgstr "Changer le mot de passe" 169 | 170 | #: resume_builder/templates/account/password_reset.html:7 171 | #: resume_builder/templates/account/password_reset.html:11 172 | #: resume_builder/templates/account/password_reset_done.html:6 173 | #: resume_builder/templates/account/password_reset_done.html:9 174 | msgid "Password Reset" 175 | msgstr "Réinitialisation du mot de passe" 176 | 177 | #: resume_builder/templates/account/password_reset.html:16 178 | msgid "" 179 | "Forgotten your password? Enter your e-mail address below, and we'll send you " 180 | "an e-mail allowing you to reset it." 181 | msgstr "" 182 | "Mot de passe oublié? Entrez votre adresse e-mail ci-dessous, et nous vous " 183 | "enverrons un e-mail vous permettant de le réinitialiser." 184 | 185 | #: resume_builder/templates/account/password_reset.html:21 186 | msgid "Reset My Password" 187 | msgstr "Réinitialiser mon mot de passe" 188 | 189 | #: resume_builder/templates/account/password_reset.html:24 190 | msgid "Please contact us if you have any trouble resetting your password." 191 | msgstr "" 192 | "Veuillez nous contacter si vous rencontrez des difficultés pour réinitialiser" 193 | "votre mot de passe." 194 | 195 | #: resume_builder/templates/account/password_reset_done.html:15 196 | msgid "" 197 | "We have sent you an e-mail. Please contact us if you do not receive it " 198 | "within a few minutes." 199 | msgstr "" 200 | "Nous vous avons envoyé un e-mail. Veuillez nous contacter si vous ne le " 201 | "recevez pas d'ici quelques minutes." 202 | 203 | #: resume_builder/templates/account/password_reset_from_key.html:8 204 | msgid "Bad Token" 205 | msgstr "Token Invalide" 206 | 207 | #: resume_builder/templates/account/password_reset_from_key.html:12 208 | #, python-format 209 | msgid "" 210 | "The password reset link was invalid, possibly because it has already been " 211 | "used. Please request a new password reset." 213 | msgstr "" 214 | "Le lien de réinitialisation du mot de passe n'était pas valide, peut-être parce " 215 | "qu'il a déjà été utilisé. Veuillez faire une " 216 | "nouvelle demande de réinitialisation de mot de passe." 217 | 218 | #: resume_builder/templates/account/password_reset_from_key.html:18 219 | msgid "change password" 220 | msgstr "changer le mot de passe" 221 | 222 | #: resume_builder/templates/account/password_reset_from_key.html:21 223 | #: resume_builder/templates/account/password_reset_from_key_done.html:8 224 | msgid "Your password is now changed." 225 | msgstr "Votre mot de passe est maintenant modifié." 226 | 227 | #: resume_builder/templates/account/password_set.html:6 228 | #: resume_builder/templates/account/password_set.html:9 229 | #: resume_builder/templates/account/password_set.html:14 230 | msgid "Set Password" 231 | msgstr "Définir le mot de passe" 232 | 233 | #: resume_builder/templates/account/signup.html:6 234 | msgid "Signup" 235 | msgstr "S'inscrire" 236 | 237 | #: resume_builder/templates/account/signup.html:9 238 | #: resume_builder/templates/account/signup.html:19 239 | #: resume_builder/templates/base.html:67 240 | msgid "Sign Up" 241 | msgstr "S'inscrire" 242 | 243 | #: resume_builder/templates/account/signup.html:11 244 | #, python-format 245 | msgid "" 246 | "Already have an account? Then please sign in." 247 | msgstr "" 248 | "Vous avez déjà un compte? Alors veuillez vous connecter." 249 | 250 | #: resume_builder/templates/account/signup_closed.html:5 251 | #: resume_builder/templates/account/signup_closed.html:8 252 | msgid "Sign Up Closed" 253 | msgstr "Inscriptions closes" 254 | 255 | #: resume_builder/templates/account/signup_closed.html:10 256 | msgid "We are sorry, but the sign up is currently closed." 257 | msgstr "Désolé, mais l'inscription est actuellement fermée." 258 | 259 | #: resume_builder/templates/account/verification_sent.html:5 260 | #: resume_builder/templates/account/verification_sent.html:8 261 | #: resume_builder/templates/account/verified_email_required.html:5 262 | #: resume_builder/templates/account/verified_email_required.html:8 263 | msgid "Verify Your E-mail Address" 264 | msgstr "Vérifiez votre adresse e-mail" 265 | 266 | #: resume_builder/templates/account/verification_sent.html:10 267 | msgid "" 268 | "We have sent an e-mail to you for verification. Follow the link provided to " 269 | "finalize the signup process. Please contact us if you do not receive it " 270 | "within a few minutes." 271 | msgstr "Nous vous avons envoyé un e-mail pour vérification. Suivez le lien fourni " 272 | "pour finalisez le processus d'inscription. Veuillez nous contacter si vous ne le " 273 | "recevez pas d'ici quelques minutes." 274 | 275 | #: resume_builder/templates/account/verified_email_required.html:12 276 | msgid "" 277 | "This part of the site requires us to verify that\n" 278 | "you are who you claim to be. For this purpose, we require that you\n" 279 | "verify ownership of your e-mail address. " 280 | msgstr "" 281 | "Cette partie du site nous oblige à vérifier que\n" 282 | "vous êtes qui vous prétendez être. Nous vous demandons donc de\n" 283 | "vérifier la propriété de votre adresse e-mail." 284 | 285 | #: resume_builder/templates/account/verified_email_required.html:16 286 | msgid "" 287 | "We have sent an e-mail to you for\n" 288 | "verification. Please click on the link inside this e-mail. Please\n" 289 | "contact us if you do not receive it within a few minutes." 290 | msgstr "" 291 | "Nous vous avons envoyé un e-mail pour\n" 292 | "vérification. Veuillez cliquer sur le lien contenu dans cet e-mail. Veuillez nous\n" 293 | "contacter si vous ne le recevez pas d'ici quelques minutes." 294 | 295 | #: resume_builder/templates/account/verified_email_required.html:20 296 | #, python-format 297 | msgid "" 298 | "Note: you can still change your e-" 299 | "mail address." 300 | msgstr "" 301 | "Remarque : vous pouvez toujours changer votre e-" 302 | "adresse e-mail." 303 | 304 | #: resume_builder/templates/base.html:57 305 | msgid "My Profile" 306 | msgstr "Mon Profil" 307 | 308 | #: resume_builder/users/admin.py:17 309 | msgid "Personal info" 310 | msgstr "Personal info" 311 | 312 | #: resume_builder/users/admin.py:19 313 | msgid "Permissions" 314 | msgstr "Permissions" 315 | 316 | #: resume_builder/users/admin.py:30 317 | msgid "Important dates" 318 | msgstr "Dates importantes" 319 | 320 | #: resume_builder/users/apps.py:7 321 | msgid "Users" 322 | msgstr "Utilisateurs" 323 | 324 | #: resume_builder/users/forms.py:24 325 | #: resume_builder/users/tests/test_forms.py:36 326 | msgid "This username has already been taken." 327 | msgstr "Ce nom d'utilisateur est déjà pris." 328 | 329 | #: resume_builder/users/models.py:15 330 | msgid "Name of User" 331 | msgstr "Nom de l'utilisateur" 332 | 333 | #: resume_builder/users/views.py:23 334 | msgid "Information successfully updated" 335 | msgstr "Informations mises à jour avec succès" 336 | -------------------------------------------------------------------------------- /Phase_3/config/settings/base.py: -------------------------------------------------------------------------------- 1 | """Base settings to build other settings files upon.""" 2 | 3 | from pathlib import Path 4 | 5 | import environ 6 | 7 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent.parent 8 | # resume_builder/ 9 | APPS_DIR = BASE_DIR / "resume_builder" 10 | env = environ.Env() 11 | 12 | READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=True) 13 | if READ_DOT_ENV_FILE: 14 | # OS environment variables take precedence over variables from .env 15 | env.read_env(str(BASE_DIR / ".env")) 16 | 17 | # GENERAL 18 | # ------------------------------------------------------------------------------ 19 | # https://docs.djangoproject.com/en/dev/ref/settings/#debug 20 | DEBUG = env.bool("DJANGO_DEBUG", False) 21 | # Local time zone. Choices are 22 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 23 | # though not all of them may be available with every OS. 24 | # In Windows, this must be set to your system time zone. 25 | TIME_ZONE = "UTC" 26 | # https://docs.djangoproject.com/en/dev/ref/settings/#language-code 27 | LANGUAGE_CODE = "en-us" 28 | # https://docs.djangoproject.com/en/dev/ref/settings/#languages 29 | # from django.utils.translation import gettext_lazy as _ 30 | # LANGUAGES = [ 31 | # ('en', _('English')), 32 | # ('fr-fr', _('French')), 33 | # ('pt-br', _('Portuguese')), 34 | # ] 35 | # https://docs.djangoproject.com/en/dev/ref/settings/#site-id 36 | SITE_ID = 1 37 | # https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n 38 | USE_I18N = True 39 | # https://docs.djangoproject.com/en/dev/ref/settings/#use-tz 40 | USE_TZ = True 41 | # https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths 42 | LOCALE_PATHS = [str(BASE_DIR / "locale")] 43 | 44 | # DATABASES 45 | # ------------------------------------------------------------------------------ 46 | # https://docs.djangoproject.com/en/dev/ref/settings/#databases 47 | DATABASES = { 48 | "default": { 49 | "ENGINE": "django.db.backends.postgresql_psycopg2", 50 | "NAME": env("POSTGRES_DB", default="mylife"), 51 | "USER": env("POSTGRES_USER", default="postgres"), 52 | "PASSWORD": env("POSTGRES_PASSWORD", default="securepassword"), 53 | "HOST": env("POSTGRES_HOST", default="localhost"), 54 | "PORT": env("POSTGRES_PORT", default="5432"), 55 | } 56 | } 57 | DATABASES["default"]["ATOMIC_REQUESTS"] = True 58 | # https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-DEFAULT_AUTO_FIELD 59 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 60 | 61 | # URLS 62 | # ------------------------------------------------------------------------------ 63 | # https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf 64 | ROOT_URLCONF = "config.urls" 65 | # https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application 66 | WSGI_APPLICATION = "config.wsgi.application" 67 | 68 | # APPS 69 | # ------------------------------------------------------------------------------ 70 | DJANGO_APPS = [ 71 | "django.contrib.auth", 72 | "django.contrib.contenttypes", 73 | "django.contrib.sessions", 74 | "django.contrib.sites", 75 | "django.contrib.messages", 76 | "django.contrib.staticfiles", 77 | # "django.contrib.humanize", # Handy template tags 78 | "django.contrib.admin", 79 | "django.forms", 80 | ] 81 | THIRD_PARTY_APPS = [ 82 | "crispy_forms", 83 | "crispy_bootstrap5", 84 | "allauth", 85 | "allauth.account", 86 | "allauth.socialaccount", 87 | "rest_framework", 88 | "rest_framework.authtoken", 89 | "corsheaders", 90 | "drf_spectacular", 91 | "drf_yasg", 92 | ] 93 | 94 | LOCAL_APPS = [ 95 | "resume_builder.users", 96 | "resume_builder.resume", 97 | # Your stuff: custom apps go here 98 | ] 99 | # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps 100 | INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS 101 | 102 | # MIGRATIONS 103 | # ------------------------------------------------------------------------------ 104 | # https://docs.djangoproject.com/en/dev/ref/settings/#migration-modules 105 | MIGRATION_MODULES = {"sites": "resume_builder.contrib.sites.migrations"} 106 | 107 | # AUTHENTICATION 108 | # ------------------------------------------------------------------------------ 109 | # https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends 110 | AUTHENTICATION_BACKENDS = [ 111 | "django.contrib.auth.backends.ModelBackend", 112 | "allauth.account.auth_backends.AuthenticationBackend", 113 | ] 114 | # https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model 115 | AUTH_USER_MODEL = "users.User" 116 | # https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url 117 | LOGIN_REDIRECT_URL = "users:redirect" 118 | # https://docs.djangoproject.com/en/dev/ref/settings/#login-url 119 | LOGIN_URL = "account_login" 120 | 121 | # PASSWORDS 122 | # ------------------------------------------------------------------------------ 123 | # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers 124 | PASSWORD_HASHERS = [ 125 | # https://docs.djangoproject.com/en/dev/topics/auth/passwords/#using-argon2-with-django 126 | "django.contrib.auth.hashers.Argon2PasswordHasher", 127 | "django.contrib.auth.hashers.PBKDF2PasswordHasher", 128 | "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", 129 | "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", 130 | ] 131 | # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators 132 | AUTH_PASSWORD_VALIDATORS = [ 133 | {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, 134 | {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, 135 | {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, 136 | {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, 137 | ] 138 | 139 | # MIDDLEWARE 140 | # ------------------------------------------------------------------------------ 141 | # https://docs.djangoproject.com/en/dev/ref/settings/#middleware 142 | MIDDLEWARE = [ 143 | "django.middleware.security.SecurityMiddleware", 144 | "corsheaders.middleware.CorsMiddleware", 145 | "whitenoise.middleware.WhiteNoiseMiddleware", 146 | "django.contrib.sessions.middleware.SessionMiddleware", 147 | "django.middleware.locale.LocaleMiddleware", 148 | "django.middleware.common.CommonMiddleware", 149 | "django.middleware.csrf.CsrfViewMiddleware", 150 | "django.contrib.auth.middleware.AuthenticationMiddleware", 151 | "django.contrib.messages.middleware.MessageMiddleware", 152 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 153 | "allauth.account.middleware.AccountMiddleware", 154 | ] 155 | 156 | # STATIC 157 | # ------------------------------------------------------------------------------ 158 | # https://docs.djangoproject.com/en/dev/ref/settings/#static-root 159 | STATIC_ROOT = str(BASE_DIR / "staticfiles") 160 | # https://docs.djangoproject.com/en/dev/ref/settings/#static-url 161 | STATIC_URL = "/static/" 162 | # https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS 163 | STATICFILES_DIRS = [str(APPS_DIR / "static")] 164 | # https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders 165 | STATICFILES_FINDERS = [ 166 | "django.contrib.staticfiles.finders.FileSystemFinder", 167 | "django.contrib.staticfiles.finders.AppDirectoriesFinder", 168 | ] 169 | 170 | # MEDIA 171 | # ------------------------------------------------------------------------------ 172 | # https://docs.djangoproject.com/en/dev/ref/settings/#media-root 173 | MEDIA_ROOT = str(APPS_DIR / "media") 174 | # https://docs.djangoproject.com/en/dev/ref/settings/#media-url 175 | MEDIA_URL = "/media/" 176 | 177 | # TEMPLATES 178 | # ------------------------------------------------------------------------------ 179 | # https://docs.djangoproject.com/en/dev/ref/settings/#templates 180 | TEMPLATES = [ 181 | { 182 | # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND 183 | "BACKEND": "django.template.backends.django.DjangoTemplates", 184 | # https://docs.djangoproject.com/en/dev/ref/settings/#dirs 185 | "DIRS": [str(APPS_DIR / "templates")], 186 | # https://docs.djangoproject.com/en/dev/ref/settings/#app-dirs 187 | "APP_DIRS": True, 188 | "OPTIONS": { 189 | # https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors 190 | "context_processors": [ 191 | "django.template.context_processors.debug", 192 | "django.template.context_processors.request", 193 | "django.contrib.auth.context_processors.auth", 194 | "django.template.context_processors.i18n", 195 | "django.template.context_processors.media", 196 | "django.template.context_processors.static", 197 | "django.template.context_processors.tz", 198 | "django.contrib.messages.context_processors.messages", 199 | "resume_builder.users.context_processors.allauth_settings", 200 | ], 201 | }, 202 | } 203 | ] 204 | 205 | # https://docs.djangoproject.com/en/dev/ref/settings/#form-renderer 206 | FORM_RENDERER = "django.forms.renderers.TemplatesSetting" 207 | 208 | # http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs 209 | CRISPY_TEMPLATE_PACK = "bootstrap5" 210 | CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" 211 | 212 | # FIXTURES 213 | # ------------------------------------------------------------------------------ 214 | # https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs 215 | FIXTURE_DIRS = (str(APPS_DIR / "fixtures"),) 216 | 217 | # SECURITY 218 | # ------------------------------------------------------------------------------ 219 | # https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly 220 | SESSION_COOKIE_HTTPONLY = True 221 | # https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly 222 | CSRF_COOKIE_HTTPONLY = True 223 | # https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options 224 | X_FRAME_OPTIONS = "DENY" 225 | 226 | # EMAIL 227 | # ------------------------------------------------------------------------------ 228 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend 229 | EMAIL_BACKEND = env( 230 | "DJANGO_EMAIL_BACKEND", 231 | default="django.core.mail.backends.smtp.EmailBackend", 232 | ) 233 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-timeout 234 | EMAIL_TIMEOUT = 5 235 | 236 | # ADMIN 237 | # ------------------------------------------------------------------------------ 238 | # Django Admin URL. 239 | ADMIN_URL = "admin/" 240 | # https://docs.djangoproject.com/en/dev/ref/settings/#admins 241 | ADMINS = [("""Alireza Parvaresh""", "alireza-parvaresh@example.com")] 242 | # https://docs.djangoproject.com/en/dev/ref/settings/#managers 243 | MANAGERS = ADMINS 244 | # https://cookiecutter-django.readthedocs.io/en/latest/settings.html#other-environment-settings 245 | # Force the `admin` sign in process to go through the `django-allauth` workflow 246 | DJANGO_ADMIN_FORCE_ALLAUTH = env.bool("DJANGO_ADMIN_FORCE_ALLAUTH", default=False) 247 | 248 | # LOGGING 249 | # ------------------------------------------------------------------------------ 250 | # https://docs.djangoproject.com/en/dev/ref/settings/#logging 251 | # See https://docs.djangoproject.com/en/dev/topics/logging for 252 | # more details on how to customize your logging configuration. 253 | LOGGING = { 254 | "version": 1, 255 | "disable_existing_loggers": False, 256 | "formatters": { 257 | "verbose": { 258 | "format": "%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s", 259 | }, 260 | }, 261 | "handlers": { 262 | "console": { 263 | "level": "DEBUG", 264 | "class": "logging.StreamHandler", 265 | "formatter": "verbose", 266 | } 267 | }, 268 | "root": {"level": "INFO", "handlers": ["console"]}, 269 | } 270 | 271 | 272 | # django-allauth 273 | # ------------------------------------------------------------------------------ 274 | ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True) 275 | # https://docs.allauth.org/en/latest/account/configuration.html 276 | ACCOUNT_AUTHENTICATION_METHOD = "email" 277 | # https://docs.allauth.org/en/latest/account/configuration.html 278 | ACCOUNT_EMAIL_REQUIRED = True 279 | # https://docs.allauth.org/en/latest/account/configuration.html 280 | ACCOUNT_USERNAME_REQUIRED = False 281 | # https://docs.allauth.org/en/latest/account/configuration.html 282 | ACCOUNT_USER_MODEL_USERNAME_FIELD = None 283 | # https://docs.allauth.org/en/latest/account/configuration.html 284 | ACCOUNT_EMAIL_VERIFICATION = "mandatory" 285 | # https://docs.allauth.org/en/latest/account/configuration.html 286 | ACCOUNT_ADAPTER = "resume_builder.users.adapters.AccountAdapter" 287 | # https://docs.allauth.org/en/latest/account/forms.html 288 | ACCOUNT_FORMS = {"signup": "resume_builder.users.forms.UserSignupForm"} 289 | # https://docs.allauth.org/en/latest/socialaccount/configuration.html 290 | SOCIALACCOUNT_ADAPTER = "resume_builder.users.adapters.SocialAccountAdapter" 291 | # https://docs.allauth.org/en/latest/socialaccount/configuration.html 292 | SOCIALACCOUNT_FORMS = {"signup": "resume_builder.users.forms.UserSocialSignupForm"} 293 | 294 | # django-rest-framework 295 | # ------------------------------------------------------------------------------- 296 | # django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/ 297 | REST_FRAMEWORK = { 298 | "DEFAULT_AUTHENTICATION_CLASSES": ( 299 | "rest_framework.authentication.SessionAuthentication", 300 | "rest_framework.authentication.TokenAuthentication", 301 | ), 302 | # "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), 303 | "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", 304 | } 305 | 306 | # django-cors-headers - https://github.com/adamchainz/django-cors-headers#setup 307 | CORS_URLS_REGEX = r"^/api/.*$" 308 | 309 | # By Default swagger ui is available only to admin user(s). You can change permission classes to change that 310 | # See more configuration options at https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings 311 | SPECTACULAR_SETTINGS = { 312 | "TITLE": "Resume Builder API", 313 | "DESCRIPTION": "Documentation of API endpoints of Resume Builder", 314 | "VERSION": "1.0.0", 315 | "SERVE_PERMISSIONS": ["rest_framework.permissions.IsAdminUser"], 316 | } 317 | # Your stuff... 318 | # ------------------------------------------------------------------------------ 319 | --------------------------------------------------------------------------------