{% if exception %}{{ exception }}{% else %}You're not allowed to access this page.{% endif %}
9 | {% endblock content %} 10 | -------------------------------------------------------------------------------- /conversa_dj/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Page not found{% endblock %} 4 | 5 | {% block content %} 6 |{% if exception %}{{ exception }}{% else %}This is not the page you were looking for.{% endif %}
9 | {% endblock content %} 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 |{% translate "This account is inactive." %}
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /conversa_dj/templates/account/password_reset_from_key_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% block head_title %}{% translate "Change Password" %}{% endblock %} 5 | 6 | {% block inner %} 7 |{% translate 'Your password is now changed.' %}
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "**/Thumbs.db": true, 9 | "venv": true, 10 | "frontend/node_modules": true 11 | }, 12 | "python.formatting.provider": "black" 13 | } -------------------------------------------------------------------------------- /conversa_dj/templates/account/signup_closed.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% translate "Sign Up Closed" %}{% endblock %} 6 | 7 | {% block inner %} 8 |{% translate "We are sorry, but the sign up is currently closed." %}
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | load-plugins=pylint_django, pylint_celery 3 | django-settings-module=config.settings.base 4 | [FORMAT] 5 | max-line-length=120 6 | 7 | [MESSAGES CONTROL] 8 | disable=missing-docstring,invalid-name 9 | 10 | [DESIGN] 11 | max-parents=13 12 | 13 | [TYPECHECK] 14 | generated-members=REQUEST,acl_users,aq_parent,"[a-zA-Z]+_set{1,2}",save,delete 15 | -------------------------------------------------------------------------------- /conversa_dj/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from conversa_dj.users.models import User 4 | from conversa_dj.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() -> User: 14 | return UserFactory() 15 | -------------------------------------------------------------------------------- /conversa_dj/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Server Error{% endblock %} 4 | 5 | {% block content %} 6 |We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.
11 | {% endblock content %} 12 | -------------------------------------------------------------------------------- /conversa_dj/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 = "conversa_dj.users" 7 | verbose_name = _("Users") 8 | 9 | def ready(self): 10 | try: 11 | import conversa_dj.users.signals # noqa F401 12 | except ImportError: 13 | pass 14 | -------------------------------------------------------------------------------- /compose/production/aws/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM garland/aws-cli-docker:1.15.47 2 | 3 | COPY ./compose/production/aws/maintenance /usr/local/bin/maintenance 4 | COPY ./compose/production/postgres/maintenance/_sourced /usr/local/bin/maintenance/_sourced 5 | 6 | RUN chmod +x /usr/local/bin/maintenance/* 7 | 8 | RUN mv /usr/local/bin/maintenance/* /usr/local/bin \ 9 | && rmdir /usr/local/bin/maintenance 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /frontend/src/components/ProtectedRoute.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { Navigate } from "react-router-dom"; 3 | import { AuthContext } from "../contexts/AuthContext"; 4 | 5 | export function ProtectedRoute({ children }: { children: any }) { 6 | const { user } = useContext(AuthContext); 7 | if (!user) { 8 | return{% 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 %}
11 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /conversa_dj/templates/users/user_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block title %}{{ user.username }}{% endblock %} 5 | 6 | {% block content %} 7 |{% blocktranslate %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktranslate %}
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /conversa_dj/templates/account/password_set.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %}{% translate "Set Password" %}{% endblock %} 7 | 8 | {% block inner %} 9 |{% translate 'Are you sure you want to sign out?' %}
11 | 12 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /conversa_dj/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 | -------------------------------------------------------------------------------- /conversa_dj/users/adapters.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from allauth.account.adapter import DefaultAccountAdapter 4 | from allauth.socialaccount.adapter import DefaultSocialAccountAdapter 5 | from django.conf import settings 6 | from django.http import HttpRequest 7 | 8 | 9 | class AccountAdapter(DefaultAccountAdapter): 10 | def is_open_for_signup(self, request: HttpRequest): 11 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) 12 | 13 | 14 | class SocialAccountAdapter(DefaultSocialAccountAdapter): 15 | def is_open_for_signup(self, request: HttpRequest, sociallogin: Any): 16 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) 17 | -------------------------------------------------------------------------------- /conversa_dj/users/tests/test_swagger.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.urls import reverse 3 | 4 | pytestmark = pytest.mark.django_db 5 | 6 | 7 | def test_swagger_accessible_by_admin(admin_client): 8 | url = reverse("api-docs") 9 | response = admin_client.get(url) 10 | assert response.status_code == 200 11 | 12 | 13 | def test_swagger_ui_not_accessible_by_normal_user(client): 14 | url = reverse("api-docs") 15 | response = client.get(url) 16 | assert response.status_code == 403 17 | 18 | 19 | def test_api_schema_generated_successfully(admin_client): 20 | url = reverse("api-schema") 21 | response = admin_client.get(url) 22 | assert response.status_code == 200 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.idea/runConfigurations/docker_compose_up_django.xml: -------------------------------------------------------------------------------- 1 |{% blocktranslate %}Already have an account? Then please sign in.{% endblocktranslate %}
12 | 13 | 21 | 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /frontend/src/services/AuthService.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { UserModel } from "../models/User"; 3 | 4 | 5 | class AuthService { 6 | setUserInLocalStorage(data: UserModel) { 7 | localStorage.setItem("user", JSON.stringify(data)); 8 | } 9 | 10 | async login(username: string, password: string): Promise{{ object.name }}
15 | {% endif %} 16 |{% 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 |{% blocktranslate %}We have sent an e-mail to you for 17 | verification. Please click on the link inside this e-mail. Please 18 | contact us if you do not receive it within a few minutes.{% endblocktranslate %}
19 | 20 |{% blocktranslate %}Note: you can still change your e-mail address.{% endblocktranslate %}
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /compose/production/aws/maintenance/upload: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### Upload the /backups folder to Amazon S3 4 | ### 5 | ### Usage: 6 | ### $ docker-compose -f production.yml run --rm awscli upload 7 | 8 | set -o errexit 9 | set -o pipefail 10 | set -o nounset 11 | 12 | working_dir="$(dirname ${0})" 13 | source "${working_dir}/_sourced/constants.sh" 14 | source "${working_dir}/_sourced/messages.sh" 15 | 16 | export AWS_ACCESS_KEY_ID="${DJANGO_AWS_ACCESS_KEY_ID}" 17 | export AWS_SECRET_ACCESS_KEY="${DJANGO_AWS_SECRET_ACCESS_KEY}" 18 | export AWS_STORAGE_BUCKET_NAME="${DJANGO_AWS_STORAGE_BUCKET_NAME}" 19 | 20 | 21 | message_info "Upload the backups directory to S3 bucket {$AWS_STORAGE_BUCKET_NAME}" 22 | 23 | aws s3 cp ${BACKUP_DIR_PATH} s3://${AWS_STORAGE_BUCKET_NAME}${BACKUP_DIR_PATH} --recursive 24 | 25 | message_info "Cleaning the directory ${BACKUP_DIR_PATH}" 26 | 27 | rm -rf ${BACKUP_DIR_PATH}/* 28 | 29 | message_success "Finished uploading and cleaning." 30 | -------------------------------------------------------------------------------- /conversa_dj/users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db.models import CharField 3 | from django.urls import reverse 4 | from django.utils.translation import gettext_lazy as _ 5 | 6 | 7 | class User(AbstractUser): 8 | """ 9 | Default custom user model for Conversa Django. 10 | If adding fields that need to be filled at user signup, 11 | check forms.SignupForm and forms.SocialSignupForms accordingly. 12 | """ 13 | 14 | #: First and last name do not cover name patterns around the globe 15 | name = CharField(_("Name of User"), blank=True, max_length=255) 16 | first_name = None # type: ignore 17 | last_name = None # type: ignore 18 | 19 | def get_absolute_url(self): 20 | """Get url for user's detail view. 21 | 22 | Returns: 23 | str: URL for user detail. 24 | 25 | """ 26 | return reverse("users:detail", kwargs={"username": self.username}) 27 | -------------------------------------------------------------------------------- /conversa_dj/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 %}{% translate "Password Reset" %}{% endblock %} 8 | 9 | {% block inner %} 10 | 11 |{% 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 | 23 | 24 |{% blocktranslate %}Please contact us if you have any trouble resetting your password.{% endblocktranslate %}
25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /conversa_dj/users/tests/test_drf_views.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.test import RequestFactory 3 | 4 | from conversa_dj.users.api.views import UserViewSet 5 | from conversa_dj.users.models import User 6 | 7 | pytestmark = pytest.mark.django_db 8 | 9 | 10 | class TestUserViewSet: 11 | def test_get_queryset(self, user: User, rf: RequestFactory): 12 | view = UserViewSet() 13 | request = rf.get("/fake-url/") 14 | request.user = user 15 | 16 | view.request = request 17 | 18 | assert user in view.get_queryset() 19 | 20 | def test_me(self, user: User, rf: RequestFactory): 21 | view = UserViewSet() 22 | request = rf.get("/fake-url/") 23 | request.user = user 24 | 25 | view.request = request 26 | 27 | response = view.me(request) 28 | 29 | assert response.data == { 30 | "username": user.username, 31 | "name": user.name, 32 | "url": f"http://testserver/api/users/{user.username}/", 33 | } 34 | -------------------------------------------------------------------------------- /conversa_dj/users/tests/factories.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Sequence 2 | 3 | from django.contrib.auth import get_user_model 4 | from factory import Faker, post_generation 5 | from factory.django import DjangoModelFactory 6 | 7 | 8 | class UserFactory(DjangoModelFactory): 9 | 10 | username = Faker("user_name") 11 | email = Faker("email") 12 | name = Faker("name") 13 | 14 | @post_generation 15 | def password(self, create: bool, extracted: Sequence[Any], **kwargs): 16 | password = ( 17 | extracted 18 | if extracted 19 | else Faker( 20 | "password", 21 | length=42, 22 | special_chars=True, 23 | digits=True, 24 | upper_case=True, 25 | lower_case=True, 26 | ).evaluate(None, None, extra={"locale": None}) 27 | ) 28 | self.set_password(password) 29 | 30 | class Meta: 31 | model = get_user_model() 32 | django_get_or_create = ["username"] 33 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: "^docs/|/migrations/" 2 | default_stages: [commit] 3 | 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v4.2.0 7 | hooks: 8 | - id: trailing-whitespace 9 | - id: end-of-file-fixer 10 | - id: check-yaml 11 | 12 | - repo: https://github.com/asottile/pyupgrade 13 | rev: v2.32.1 14 | hooks: 15 | - id: pyupgrade 16 | args: [--py39-plus] 17 | 18 | - repo: https://github.com/psf/black 19 | rev: 22.3.0 20 | hooks: 21 | - id: black 22 | 23 | - repo: https://github.com/PyCQA/isort 24 | rev: 5.10.1 25 | hooks: 26 | - id: isort 27 | 28 | - repo: https://github.com/PyCQA/flake8 29 | rev: 4.0.1 30 | hooks: 31 | - id: flake8 32 | args: ["--config=setup.cfg"] 33 | additional_dependencies: [flake8-isort] 34 | 35 | # sets up .pre-commit-ci.yaml to ensure pre-commit dependencies stay up to date 36 | ci: 37 | autoupdate_schedule: weekly 38 | skip: [] 39 | submodules: false 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | Copyright (c) 2022, Matthew Freire 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 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv 4 | 5 | [pycodestyle] 6 | max-line-length = 120 7 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv 8 | 9 | [isort] 10 | line_length = 88 11 | known_first_party = conversa_dj,config 12 | multi_line_output = 3 13 | default_section = THIRDPARTY 14 | skip = venv/ 15 | skip_glob = **/migrations/*.py 16 | include_trailing_comma = true 17 | force_grid_wrap = 0 18 | use_parentheses = true 19 | 20 | [mypy] 21 | python_version = 3.9 22 | check_untyped_defs = True 23 | ignore_missing_imports = True 24 | warn_unused_ignores = True 25 | warn_redundant_casts = True 26 | warn_unused_configs = True 27 | plugins = mypy_django_plugin.main, mypy_drf_plugin.main 28 | 29 | [mypy.plugins.django-stubs] 30 | django_settings_module = config.settings.test 31 | 32 | [mypy-*.migrations.*] 33 | # Django migrations should not produce any errors: 34 | ignore_errors = True 35 | 36 | [coverage:run] 37 | include = conversa_dj/* 38 | omit = *migrations*, *tests* 39 | plugins = 40 | django_coverage_plugin 41 | -------------------------------------------------------------------------------- /compose/production/django/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | 8 | 9 | # N.B. If only .env files supported variable expansion... 10 | export CELERY_BROKER_URL="${REDIS_URL}" 11 | 12 | 13 | if [ -z "${POSTGRES_USER}" ]; then 14 | base_postgres_image_default_user='postgres' 15 | export POSTGRES_USER="${base_postgres_image_default_user}" 16 | fi 17 | export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" 18 | 19 | postgres_ready() { 20 | python << END 21 | import sys 22 | 23 | import psycopg2 24 | 25 | try: 26 | psycopg2.connect( 27 | dbname="${POSTGRES_DB}", 28 | user="${POSTGRES_USER}", 29 | password="${POSTGRES_PASSWORD}", 30 | host="${POSTGRES_HOST}", 31 | port="${POSTGRES_PORT}", 32 | ) 33 | except psycopg2.OperationalError: 34 | sys.exit(-1) 35 | sys.exit(0) 36 | 37 | END 38 | } 39 | until postgres_ready; do 40 | >&2 echo 'Waiting for PostgreSQL to become available...' 41 | sleep 1 42 | done 43 | >&2 echo 'PostgreSQL is available' 44 | 45 | exec "$@" 46 | -------------------------------------------------------------------------------- /conversa_dj/templates/account/email_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %}{% translate "Confirm E-mail Address" %}{% endblock %} 7 | 8 | 9 | {% block inner %} 10 |{% blocktranslate with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktranslate %}
17 | 18 | 22 | 23 | {% else %} 24 | 25 | {% url 'account_email' as email_url %} 26 | 27 |{% blocktranslate %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktranslate %}
28 | 29 | {% endif %} 30 | 31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /conversa_dj/templates/account/password_reset_from_key.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | {% block head_title %}{% translate "Change Password" %}{% endblock %} 6 | 7 | {% block inner %} 8 |{% blocktranslate %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktranslate %}
13 | {% else %} 14 | {% if form %} 15 | 20 | {% else %} 21 |{% translate 'Your password is now changed.' %}
22 | {% endif %} 23 | {% endif %} 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /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 | # conversa_dj directory. 28 | current_path = Path(__file__).parent.resolve() 29 | sys.path.append(str(current_path / "conversa_dj")) 30 | 31 | execute_from_command_line(sys.argv) 32 | -------------------------------------------------------------------------------- /conversa_dj/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth import admin as auth_admin 3 | from django.contrib.auth import get_user_model 4 | from django.utils.translation import gettext_lazy as _ 5 | 6 | from conversa_dj.users.forms import UserAdminChangeForm, UserAdminCreationForm 7 | 8 | User = get_user_model() 9 | 10 | 11 | @admin.register(User) 12 | class UserAdmin(auth_admin.UserAdmin): 13 | 14 | form = UserAdminChangeForm 15 | add_form = UserAdminCreationForm 16 | fieldsets = ( 17 | (None, {"fields": ("username", "password")}), 18 | (_("Personal info"), {"fields": ("name", "email")}), 19 | ( 20 | _("Permissions"), 21 | { 22 | "fields": ( 23 | "is_active", 24 | "is_staff", 25 | "is_superuser", 26 | "groups", 27 | "user_permissions", 28 | ), 29 | }, 30 | ), 31 | (_("Important dates"), {"fields": ("last_login", "date_joined")}), 32 | ) 33 | list_display = ["username", "name", "is_superuser"] 34 | search_fields = ["name"] 35 | -------------------------------------------------------------------------------- /.idea/runConfigurations/merge_production_dotenvs_in_dotenv.xml: -------------------------------------------------------------------------------- 1 |17 | {% translate "Please sign in with one of your existing third party accounts:" %} 18 | {% if ACCOUNT_ALLOW_REGISTRATION %} 19 | {% blocktranslate trimmed %} 20 | Or, sign up 21 | for a {{ site_name }} account and sign in below: 22 | {% endblocktranslate %} 23 | {% endif %} 24 |
25 | 26 | 35 | 36 | {% include "socialaccount/snippets/login_extra.html" %} 37 | 38 | {% else %} 39 | {% if ACCOUNT_ALLOW_REGISTRATION %} 40 |41 | {% blocktranslate trimmed %} 42 | If you have not created an account yet, then please 43 | sign up first. 44 | {% endblocktranslate %} 45 |
46 | {% endif %} 47 | {% endif %} 48 | 49 | 58 | 59 | {% endblock %} 60 | -------------------------------------------------------------------------------- /requirements/local.txt: -------------------------------------------------------------------------------- 1 | -r base.txt 2 | 3 | Werkzeug[watchdog]==2.0.3 # https://github.com/pallets/werkzeug 4 | ipdb==0.13.9 # https://github.com/gotcha/ipdb 5 | psycopg2==2.9.3 # https://github.com/psycopg/psycopg2 6 | watchgod==0.8.2 # https://github.com/samuelcolvin/watchgod 7 | 8 | # Testing 9 | # ------------------------------------------------------------------------------ 10 | mypy==0.950 # https://github.com/python/mypy 11 | django-stubs==1.9.0 # https://github.com/typeddjango/django-stubs 12 | pytest==7.1.2 # https://github.com/pytest-dev/pytest 13 | pytest-sugar==0.9.4 # https://github.com/Frozenball/pytest-sugar 14 | djangorestframework-stubs==1.4.0 # https://github.com/typeddjango/djangorestframework-stubs 15 | 16 | # Documentation 17 | # ------------------------------------------------------------------------------ 18 | sphinx==4.5.0 # https://github.com/sphinx-doc/sphinx 19 | sphinx-autobuild==2021.3.14 # https://github.com/GaretJax/sphinx-autobuild 20 | 21 | # Code quality 22 | # ------------------------------------------------------------------------------ 23 | flake8==4.0.1 # https://github.com/PyCQA/flake8 24 | flake8-isort==4.1.1 # https://github.com/gforcada/flake8-isort 25 | coverage==6.3.3 # https://github.com/nedbat/coveragepy 26 | black==22.3.0 # https://github.com/psf/black 27 | pylint-django==2.5.3 # https://github.com/PyCQA/pylint-django 28 | pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery 29 | pre-commit==2.19.0 # https://github.com/pre-commit/pre-commit 30 | 31 | # Django 32 | # ------------------------------------------------------------------------------ 33 | factory-boy==3.2.1 # https://github.com/FactoryBoy/factory_boy 34 | 35 | django-debug-toolbar==3.4.0 # https://github.com/jazzband/django-debug-toolbar 36 | django-extensions==3.1.5 # https://github.com/django-extensions/django-extensions 37 | django-coverage-plugin==2.0.3 # https://github.com/nedbat/django_coverage_plugin 38 | pytest-django==4.5.2 # https://github.com/pytest-dev/pytest-django 39 | -------------------------------------------------------------------------------- /compose/production/traefik/traefik.yml: -------------------------------------------------------------------------------- 1 | log: 2 | level: INFO 3 | 4 | entryPoints: 5 | web: 6 | # http 7 | address: ":80" 8 | http: 9 | # https://docs.traefik.io/routing/entrypoints/#entrypoint 10 | redirections: 11 | entryPoint: 12 | to: web-secure 13 | 14 | web-secure: 15 | # https 16 | address: ":443" 17 | 18 | flower: 19 | address: ":5555" 20 | 21 | certificatesResolvers: 22 | letsencrypt: 23 | # https://docs.traefik.io/master/https/acme/#lets-encrypt 24 | acme: 25 | email: "matt@justdjango.com" 26 | storage: /etc/traefik/acme/acme.json 27 | # https://docs.traefik.io/master/https/acme/#httpchallenge 28 | httpChallenge: 29 | entryPoint: web 30 | 31 | http: 32 | routers: 33 | web-secure-router: 34 | rule: "Host(`chat.justdjango.com`)" 35 | entryPoints: 36 | - web-secure 37 | middlewares: 38 | - csrf 39 | service: django 40 | tls: 41 | # https://docs.traefik.io/master/routing/routers/#certresolver 42 | certResolver: letsencrypt 43 | 44 | flower-secure-router: 45 | rule: "Host(`chat.justdjango.com`)" 46 | entryPoints: 47 | - flower 48 | service: flower 49 | tls: 50 | # https://docs.traefik.io/master/routing/routers/#certresolver 51 | certResolver: letsencrypt 52 | 53 | middlewares: 54 | csrf: 55 | # https://docs.traefik.io/master/middlewares/headers/#hostsproxyheaders 56 | # https://docs.djangoproject.com/en/dev/ref/csrf/#ajax 57 | headers: 58 | hostsProxyHeaders: ["X-CSRFToken"] 59 | 60 | services: 61 | django: 62 | loadBalancer: 63 | servers: 64 | - url: http://django:5000 65 | 66 | flower: 67 | loadBalancer: 68 | servers: 69 | - url: http://flower:5555 70 | 71 | providers: 72 | # https://docs.traefik.io/master/providers/file/ 73 | file: 74 | filename: /etc/traefik/traefik.yml 75 | watch: true 76 | -------------------------------------------------------------------------------- /frontend/src/components/ActiveConversations.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { AuthContext } from "../contexts/AuthContext"; 4 | import { ConversationModel } from "../models/Conversation"; 5 | 6 | export function ActiveConversations() { 7 | const { user } = useContext(AuthContext); 8 | const [conversations, setActiveConversations] = useState{c.last_message?.content}
49 |50 | {formatMessageTimestamp(c.last_message?.timestamp)} 51 |
52 |{% translate 'The following e-mail addresses are associated with your account:' %}
14 | 15 | 44 | 45 | {% else %} 46 |{% 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." %}
47 | 48 | {% endif %} 49 | 50 | 51 |Use this document as a way to quick start any new project.
96 | {% endblock content %} 97 | 98 |typing...
215 | )} 216 |