├── .gitignore ├── README.md ├── comments ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20200529_2314.py │ ├── 0003_auto_20200529_2331.py │ ├── 0004_remove_mycomment_ans.py │ ├── 0005_auto_20200530_2105.py │ └── __init__.py ├── models.py ├── tests.py └── views.py ├── config ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py ├── fixtures ├── qnA.json └── users.json ├── manage.py ├── notifications ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_notification_is_anonymous.py │ ├── 0003_auto_20200627_2320.py │ ├── 0004_auto_20200628_0011.py │ ├── 0005_auto_20200629_0251.py │ ├── 0006_notification_is_requested_question.py │ ├── 0007_notification_is_following_user.py │ ├── 0008_auto_20200705_0351.py │ └── __init__.py ├── models.py ├── templatetags │ ├── __init__.py │ └── notifications.py ├── tests.py ├── urls.py └── views.py ├── qnA ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── get_topics.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20200524_1128.py │ ├── 0003_answer_question.py │ ├── 0004_auto_20200527_0856.py │ ├── 0005_auto_20200527_1754.py │ ├── 0006_auto_20200527_2352.py │ ├── 0007_question_rank.py │ ├── 0008_auto_20200610_1527.py │ ├── 0009_auto_20200611_1454.py │ ├── 0010_answer_pin_answer.py │ ├── 0011_remove_question_description.py │ ├── 0012_followquestion.py │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── requirements.txt ├── screenshots └── home.png ├── static ├── css │ ├── bootstrap.min.css │ └── styles.css ├── imgs │ ├── logo.png │ ├── no_display_img.jpg │ └── qnA │ │ ├── briefcase.svg │ │ ├── building.svg │ │ ├── caret-down-square-fill.svg │ │ ├── caret-down-square.svg │ │ ├── caret-up-square-fill.svg │ │ ├── caret-up-square.svg │ │ ├── map.svg │ │ ├── pencil-square.svg │ │ └── vectorpaint.svg └── js │ ├── bootstrap.min.js │ ├── dismiss_notifications.js │ ├── follow_toggler.js │ ├── jquery.min.js │ ├── popper.min.js │ ├── request_answer.js │ └── vote.js ├── templates ├── base.html ├── comments │ ├── form.html │ └── preview.html ├── django_comments_xtd │ ├── comment_tree.html │ ├── email_confirmation_request.html │ ├── email_confirmation_request.txt │ └── reply.html ├── notifications │ ├── base.html │ ├── notification_detail.html │ └── notification_list.html ├── qnA │ ├── answer_confirm_delete.html │ ├── answer_detail.html │ ├── answer_request.html │ ├── qnA.html │ ├── qnA_list.html │ └── question_detail.html └── users │ ├── base_form.html │ ├── edit.html │ ├── login.html │ ├── login_and_register.html │ ├── register.html │ ├── user_answers.html │ ├── user_detail.html │ ├── user_followers.html │ ├── user_following.html │ └── user_questions.html └── users ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations ├── 0001_initial.py ├── 0002_user_bio.py ├── 0003_user_display_img.py ├── 0004_user_slug.py ├── 0005_remove_user_slug.py ├── 0006_user_slug.py ├── 0007_auto_20200613_2329.py ├── 0008_auto_20200613_2337.py ├── 0009_auto_20200616_0030.py ├── 0010_follow.py ├── 0011_auto_20200616_0057.py ├── 0012_auto_20200616_2023.py ├── 0013_auto_20200703_2257.py ├── 0014_auto_20200703_2303.py ├── 0015_auto_20200704_0459.py └── __init__.py ├── mixins.py ├── models.py ├── templatetags ├── __init__.py └── is_following.py ├── tests.py ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # custom 132 | 133 | .media/ 134 | media/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # prashnottar-django 2 | Prashnottar, a Quora clone built using django. 3 | ![Home](/screenshots/home.png?raw=true) 4 | 5 | 6 | ## Features 7 | * **Custom Front Page Trending/Ranking Algorithm:** The project uses a custom trending/ranking algorithm which ensures maximum interaction among users. 8 | * **Topics Automatically Generated:** For every question entered by the user, topics are automatically generated using RAKE algorithm. 9 | * **Voting:** Voting functionality for questions, answers & comments. 10 | * **Nested Comments:** Threaded (Nested) comments upto 4 levels. 11 | * **Similar Questions suggestion:** In the question detail page, for every question similar questions are suggested to the user. 12 | * **Anonymous Users:** Ask/Answer questions anonymously. 13 | * **Follow/Unfollow System:** Users can follow/unfollow other users as well as questions. 14 | * **Notification System:** Robust Notification system built using django signals. 15 | * **Request System:** Users can request answers for a question from other users. 16 | * **Text Formatting:** Integration of django-summernote for better text formatting. 17 | * Many more! 18 | 19 | ## Usage 20 | 1. Clone this repo using `https://github.com/singhkumarpratik/prashnottar-django.git` 21 | 2. Install dependencies using `pip install -r requirements.txt` 22 | 3. Migrate using `python manage.py migrate` 23 | 4. Load fixtures using
24 | `python manage.py loaddata fixtures/users.json`
25 | `python manage.py loaddata fixtures/qnA.json`
26 | 5. Run server using `python manage.py runserver` 27 | 28 | ## Demo 29 | https://prashnottar-django.herokuapp.com/
30 | Test credentials:
31 | username: johndoe@gmail.com
32 | password: Wsy8r6TZW3bSk 33 | -------------------------------------------------------------------------------- /comments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/comments/__init__.py -------------------------------------------------------------------------------- /comments/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.utils.translation import ugettext_lazy as _ 3 | 4 | from django_comments_xtd.admin import XtdCommentsAdmin 5 | from comments.models import MyComment 6 | 7 | 8 | class MyCommentAdmin(XtdCommentsAdmin): 9 | list_display = ( 10 | "thread_level", 11 | "cid", 12 | "name", 13 | "content_type", 14 | "object_pk", 15 | "submit_date", 16 | "followup", 17 | "is_public", 18 | "is_removed", 19 | "vote_score", 20 | ) 21 | list_display_links = ("cid",) 22 | fieldsets = ( 23 | ( 24 | None, 25 | { 26 | "fields": ( 27 | "content_type", 28 | "object_pk", 29 | "site", 30 | "vote_score", 31 | "num_vote_up", 32 | "num_vote_down", 33 | ) 34 | }, 35 | ), 36 | ( 37 | _("Content"), 38 | { 39 | "fields": ( 40 | "user", 41 | "user_name", 42 | "user_email", 43 | "user_url", 44 | "comment", 45 | "followup", 46 | ) 47 | }, 48 | ), 49 | ( 50 | _("Metadata"), 51 | {"fields": ("submit_date", "ip_address", "is_public", "is_removed")}, 52 | ), 53 | ) 54 | 55 | 56 | admin.site.register(MyComment, MyCommentAdmin) 57 | -------------------------------------------------------------------------------- /comments/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommentsConfig(AppConfig): 5 | name = 'comments' 6 | -------------------------------------------------------------------------------- /comments/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.utils.translation import ugettext_lazy as _ 3 | 4 | from django_comments_xtd.forms import XtdCommentForm 5 | from django_comments_xtd.models import TmpXtdComment 6 | 7 | 8 | class MyCommentForm(XtdCommentForm): 9 | pass 10 | -------------------------------------------------------------------------------- /comments/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-29 17:36 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('django_comments_xtd', '0006_auto_20181204_0948'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='MyComment', 18 | fields=[ 19 | ('xtdcomment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='django_comments_xtd.XtdComment')), 20 | ('title', models.CharField(max_length=256)), 21 | ], 22 | options={ 23 | 'verbose_name': 'comment', 24 | 'verbose_name_plural': 'comments', 25 | 'ordering': ('submit_date',), 26 | 'permissions': [('can_moderate', 'Can moderate comments')], 27 | 'abstract': False, 28 | }, 29 | bases=('django_comments_xtd.xtdcomment',), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /comments/migrations/0002_auto_20200529_2314.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-29 17:44 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('qnA', '0006_auto_20200527_2352'), 11 | ('comments', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='mycomment', 17 | name='title', 18 | ), 19 | migrations.AddField( 20 | model_name='mycomment', 21 | name='ans', 22 | field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='qnA.Answer'), 23 | preserve_default=False, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /comments/migrations/0003_auto_20200529_2331.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-29 18:01 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('qnA', '0006_auto_20200527_2352'), 11 | ('comments', '0002_auto_20200529_2314'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='mycomment', 17 | name='ans', 18 | field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, to='qnA.Answer'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /comments/migrations/0004_remove_mycomment_ans.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-29 18:07 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('comments', '0003_auto_20200529_2331'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='mycomment', 15 | name='ans', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /comments/migrations/0005_auto_20200530_2105.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-30 15:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('comments', '0004_remove_mycomment_ans'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='mycomment', 15 | options={}, 16 | ), 17 | migrations.AddField( 18 | model_name='mycomment', 19 | name='num_vote_down', 20 | field=models.PositiveIntegerField(db_index=True, default=0), 21 | ), 22 | migrations.AddField( 23 | model_name='mycomment', 24 | name='num_vote_up', 25 | field=models.PositiveIntegerField(db_index=True, default=0), 26 | ), 27 | migrations.AddField( 28 | model_name='mycomment', 29 | name='vote_score', 30 | field=models.IntegerField(db_index=True, default=0), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /comments/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/comments/migrations/__init__.py -------------------------------------------------------------------------------- /comments/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django_comments_xtd.models import XtdComment 3 | from vote.models import VoteModel 4 | from users.models import User 5 | 6 | 7 | class MyComment(VoteModel, XtdComment): 8 | # user = models.ForeignKey(User, on_delete=models.CASCADE, default=18) 9 | pass 10 | -------------------------------------------------------------------------------- /comments/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /comments/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/config/__init__.py -------------------------------------------------------------------------------- /config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for config project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /config/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for config project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.6. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "eqnbdqbe)odq)6u%b*b5ia(eyqdwr-l#3l!h1s&-8qlf9@e=5#" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | AUTH_USER_MODEL = "users.User" 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.sites", 39 | "django.contrib.messages", 40 | "django.contrib.staticfiles", 41 | "users", 42 | "qnA", 43 | "comments", 44 | "notifications", 45 | ] 46 | 47 | THIRD_PARTY_APPS = [ 48 | "vote", 49 | "django_summernote", 50 | "django_comments_xtd", 51 | "django_comments", 52 | ] 53 | INSTALLED_APPS += THIRD_PARTY_APPS 54 | 55 | MIDDLEWARE = [ 56 | "django.middleware.security.SecurityMiddleware", 57 | "django.contrib.sessions.middleware.SessionMiddleware", 58 | "django.middleware.common.CommonMiddleware", 59 | "django.middleware.csrf.CsrfViewMiddleware", 60 | "django.contrib.auth.middleware.AuthenticationMiddleware", 61 | "django.contrib.messages.middleware.MessageMiddleware", 62 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 63 | ] 64 | 65 | ROOT_URLCONF = "config.urls" 66 | 67 | TEMPLATES = [ 68 | { 69 | "BACKEND": "django.template.backends.django.DjangoTemplates", 70 | "DIRS": [os.path.join(BASE_DIR, "templates")], 71 | "APP_DIRS": True, 72 | "OPTIONS": { 73 | "context_processors": [ 74 | "django.template.context_processors.debug", 75 | "django.template.context_processors.request", 76 | "django.contrib.auth.context_processors.auth", 77 | "django.contrib.messages.context_processors.messages", 78 | ], 79 | }, 80 | }, 81 | ] 82 | 83 | WSGI_APPLICATION = "config.wsgi.application" 84 | 85 | 86 | # Database 87 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 88 | 89 | DATABASES = { 90 | "default": { 91 | "ENGINE": "django.db.backends.sqlite3", 92 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"), 93 | } 94 | } 95 | 96 | 97 | # Password validation 98 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 99 | 100 | AUTH_PASSWORD_VALIDATORS = [ 101 | { 102 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 103 | }, 104 | {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",}, 105 | {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",}, 106 | {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",}, 107 | ] 108 | 109 | 110 | # Internationalization 111 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 112 | 113 | LANGUAGE_CODE = "en-us" 114 | 115 | TIME_ZONE = "UTC" 116 | 117 | USE_I18N = True 118 | 119 | USE_L10N = True 120 | 121 | USE_TZ = True 122 | 123 | 124 | # Static files (CSS, JavaScript, Images) 125 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 126 | 127 | STATIC_URL = "/static/" 128 | STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] 129 | MEDIA_URL = "/media/" 130 | MEDIA_ROOT = os.path.join(BASE_DIR, "media/") 131 | X_FRAME_OPTIONS = "SAMEORIGIN" 132 | 133 | 134 | SITE_ID = 1 135 | USE_I18N = True 136 | EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" 137 | COMMENTS_APP = "django_comments_xtd" 138 | COMMENTS_XTD_CONFIRM_EMAIL = False 139 | COMMENTS_XTD_SALT = b"es-war-einmal-una-bella-princesa-in-a-beautiful-castle" 140 | COMMENTS_XTD_MAX_THREAD_LEVEL = 4 141 | COMMENTS_XTD_MODEL = "comments.models.MyComment" 142 | COMMENTS_XTD_FORM_CLASS = "comments.forms.MyCommentForm" 143 | 144 | SUMMERNOTE_THEME = "bs4" 145 | SUMMERNOTE_CONFIG = {"summernote": {"width": "100%",}} 146 | -------------------------------------------------------------------------------- /config/urls.py: -------------------------------------------------------------------------------- 1 | """config URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include, re_path 18 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 19 | from django.conf import settings 20 | from django.conf.urls.static import static 21 | 22 | urlpatterns = [ 23 | path("admin/", admin.site.urls), 24 | path("", include("qnA.urls", namespace="qnA")), 25 | path("users/", include("users.urls", namespace="users")), 26 | path("notifications/", include("notifications.urls", namespace="notifications")), 27 | path("summernote/", include("django_summernote.urls")), 28 | re_path(r"^comments/", include("django_comments_xtd.urls")), 29 | ] 30 | if settings.DEBUG: 31 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 32 | urlpatterns += staticfiles_urlpatterns() 33 | -------------------------------------------------------------------------------- /config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /fixtures/users.json: -------------------------------------------------------------------------------- 1 | [{"model": "users.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$180000$GxJNPxz9ovP6$F5KfWWQpPL5FRmfdr7ts5+yANH+b7CNmcc57obNIv98=", "last_login": "2020-07-08T20:20:33.227Z", "is_superuser": true, "username": "suser@gmail.com", "first_name": "Super", "last_name": "User", "email": "suser@gmail.com", "is_staff": true, "is_active": true, "date_joined": "2020-06-13T17:06:42Z", "bio": "Blogger | Freelance Writer | Copywriter", "display_img": "users/971ce3c39963e0bce23b814ead4c2d98.jpg", "slug": "super-user", "location": "Mumbai, Maharashtra", "groups": [], "user_permissions": []}}, {"model": "users.user", "pk": 14, "fields": {"password": "pbkdf2_sha256$180000$UunjrC4l78JM$nnjZokYFqQSN+UuK0u9jTIoNIuss9hyjWmpFOtUURTQ=", "last_login": "2020-07-07T20:02:10.985Z", "is_superuser": false, "username": "jsmith@gmail.com", "first_name": "John", "last_name": "Smith", "email": "jsmith@gmail.com", "is_staff": false, "is_active": true, "date_joined": "2020-07-07T19:36:53.588Z", "bio": "Lorem Ipsum is simply dummy text", "display_img": "", "slug": "john-smith", "location": "New Jersey", "groups": [], "user_permissions": []}}, {"model": "users.user", "pk": 15, "fields": {"password": "pbkdf2_sha256$180000$wRT9JVZorFVV$UX3Dw3dsoH5IsaMJIOlSmyW9ufTp8lg1D9GY36BEd50=", "last_login": "2020-07-07T20:16:07.278Z", "is_superuser": false, "username": "josmith@gmail.com", "first_name": "Joe", "last_name": "Smith", "email": "josmith@gmail.com", "is_staff": false, "is_active": true, "date_joined": "2020-07-07T19:39:20.296Z", "bio": "Lorem ipsum dolor sit", "display_img": "", "slug": "joe-smith", "location": "Bangalore", "groups": [], "user_permissions": []}}, {"model": "users.user", "pk": 16, "fields": {"password": "pbkdf2_sha256$180000$KJ2BFoj5lY57$ueOQs72z3ifK5yvoPD7T0HM/69cnTXua+f41Nbq1Les=", "last_login": "2020-07-07T20:12:16.487Z", "is_superuser": false, "username": "ssmith@gmail.com", "first_name": "Steve", "last_name": "Smith", "email": "ssmith@gmail.com", "is_staff": false, "is_active": true, "date_joined": "2020-07-07T19:41:21.031Z", "bio": "", "display_img": "", "slug": "steve-smith", "location": "", "groups": [], "user_permissions": []}}, {"model": "users.user", "pk": 17, "fields": {"password": "pbkdf2_sha256$180000$uhwITrQUFLCP$ohWdpq8G0ILpSfN1ygNfBObH9qNZqMDFbNOmWiQuIdM=", "last_login": "2020-07-07T20:04:08.549Z", "is_superuser": false, "username": "ksmith@gmail.com", "first_name": "kyle", "last_name": "smith", "email": "ksmith@gmail.com", "is_staff": false, "is_active": true, "date_joined": "2020-07-07T20:04:07.479Z", "bio": "", "display_img": "", "slug": "kyle-smith", "location": "", "groups": [], "user_permissions": []}}, {"model": "users.user", "pk": 18, "fields": {"password": "pbkdf2_sha256$180000$mm5IQlTbRJSD$8Bjr5JOfqHWkVwDE95L1T57UqoBa+u82WA55Vfgciqo=", "last_login": "2020-07-07T20:11:04.498Z", "is_superuser": false, "username": "jamsmith@gmail.com", "first_name": "james", "last_name": "smith", "email": "jamsmith@gmail.com", "is_staff": false, "is_active": true, "date_joined": "2020-07-07T20:05:37.351Z", "bio": "", "display_img": "", "slug": "james-smith", "location": "", "groups": [], "user_permissions": []}}, {"model": "users.follow", "pk": 190, "fields": {"from_user": 15, "to_user": 1}}, {"model": "users.follow", "pk": 191, "fields": {"from_user": 1, "to_user": 15}}, {"model": "users.workplace", "pk": 6, "fields": {"company_name": "Google", "position": "SDE", "start_year": null, "end_year": null, "is_currently_working": false, "user": 15}}, {"model": "users.education", "pk": 3, "fields": {"school_name": "DAV", "start_year": 1983, "end_year": 1988, "is_currently_studying": false, "user": 15}}] -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /notifications/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/notifications/__init__.py -------------------------------------------------------------------------------- /notifications/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from . import models 3 | 4 | 5 | @admin.register(models.Notification) 6 | class CustomQuestionAdmin(admin.ModelAdmin): 7 | pass 8 | -------------------------------------------------------------------------------- /notifications/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class NotificationsConfig(AppConfig): 5 | name = 'notifications' 6 | -------------------------------------------------------------------------------- /notifications/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-22 19:11 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 | initial = True 11 | 12 | dependencies = [ 13 | ('qnA', '0011_remove_question_description'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Notification', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('msg', models.CharField(max_length=255)), 23 | ('is_seen', models.BooleanField(default=False)), 24 | ('from_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_from_people', to=settings.AUTH_USER_MODEL)), 25 | ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notif_question', to='qnA.Question')), 26 | ('to_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_to_people', to=settings.AUTH_USER_MODEL)), 27 | ], 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /notifications/migrations/0002_notification_is_anonymous.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-24 17:44 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('notifications', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='notification', 15 | name='is_anonymous', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /notifications/migrations/0003_auto_20200627_2320.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-27 17:50 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('notifications', '0002_notification_is_anonymous'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='notification', 15 | name='created_date', 16 | field=models.DateTimeField(auto_now=True), 17 | ), 18 | migrations.AddField( 19 | model_name='notification', 20 | name='is_answer', 21 | field=models.BooleanField(default=False), 22 | ), 23 | migrations.AddField( 24 | model_name='notification', 25 | name='is_followed_ans', 26 | field=models.BooleanField(default=False), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /notifications/migrations/0004_auto_20200628_0011.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-27 18:41 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('notifications', '0003_auto_20200627_2320'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='notification', 15 | old_name='is_followed_ans', 16 | new_name='is_followed_question', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /notifications/migrations/0005_auto_20200629_0251.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-28 21:21 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('qnA', '0012_followquestion'), 11 | ('notifications', '0004_auto_20200628_0011'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='notification', 17 | name='question', 18 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notif_question', to='qnA.Question'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /notifications/migrations/0006_notification_is_requested_question.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-07-01 21:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('notifications', '0005_auto_20200629_0251'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='notification', 15 | name='is_requested_question', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /notifications/migrations/0007_notification_is_following_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-07-04 20:03 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('notifications', '0006_notification_is_requested_question'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='notification', 15 | name='is_following_user', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /notifications/migrations/0008_auto_20200705_0351.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-07-04 22:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('notifications', '0007_notification_is_following_user'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='notification', 15 | old_name='is_following_user', 16 | new_name='is_following_user_answers', 17 | ), 18 | migrations.AddField( 19 | model_name='notification', 20 | name='is_following_user_questions', 21 | field=models.BooleanField(default=False), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /notifications/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/notifications/migrations/__init__.py -------------------------------------------------------------------------------- /notifications/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models.signals import post_save 3 | from django.dispatch import receiver 4 | from users.models import User 5 | from qnA.models import Question, Answer, FollowQuestion 6 | from users.models import Follow 7 | 8 | 9 | class Notification(models.Model): 10 | msg = models.CharField(max_length=255) 11 | is_seen = models.BooleanField(default=False) 12 | is_anonymous = models.BooleanField(default=False) 13 | is_answer = models.BooleanField(default=False) 14 | is_followed_question = models.BooleanField(default=False) 15 | is_requested_question = models.BooleanField(default=False) 16 | """ For sending notification to users who follow a user when the user asks question """ 17 | is_following_user_questions = models.BooleanField(default=False) 18 | """ For sending notification to users who follow a user when the user answers question """ 19 | is_following_user_answers = models.BooleanField(default=False) 20 | from_user = models.ForeignKey( 21 | User, on_delete=models.CASCADE, related_name="notification_from_people", 22 | ) 23 | to_user = models.ForeignKey( 24 | User, on_delete=models.CASCADE, related_name="notification_to_people" 25 | ) 26 | question = models.ForeignKey( 27 | Question, on_delete=models.CASCADE, related_name="notif_question", null=True, 28 | ) 29 | created_date = models.DateTimeField(auto_now=True) 30 | # question_slug = models.SlugField() 31 | 32 | 33 | @receiver(post_save, sender=Follow) 34 | @receiver(post_save, sender=Answer) 35 | def notification(sender, **kwargs): 36 | if kwargs["created"]: 37 | """ 38 | When a user upvotes/downvotes an answer, the answer model is modified as answer's vote_score is updated. This if condition ensures that notifications are only sent when a new answer is created and not everytime when someone upvotes/downvotes the ans 39 | """ 40 | try: 41 | from_user = kwargs.get("instance").user 42 | to_user = kwargs.get("instance").question.user 43 | question = kwargs.get("instance").question 44 | is_anonymous = kwargs.get("instance").is_anonymous 45 | if is_anonymous: 46 | msg = f"An Anonymous user answered your question: {question}" 47 | else: 48 | msg = f"{from_user.first_name} {from_user.last_name} answered your question: {question}" 49 | if from_user != to_user: 50 | """Not sending notification in scenarios such as user answering his/her own question""" 51 | Notification.objects.create( 52 | from_user=from_user, 53 | to_user=to_user, 54 | msg=msg, 55 | question=question, 56 | is_anonymous=is_anonymous, 57 | is_answer=True, 58 | ) 59 | question_followers = FollowQuestion.objects.filter(question=question) 60 | if is_anonymous: 61 | # message to the followers of a question 62 | msg = f"An Anonymous user answered a question you were following: {question}" 63 | else: 64 | msg = f"{from_user.first_name} {from_user.last_name} answered a question you were following: {question}" 65 | for follower in question_followers: 66 | if from_user != follower.user: 67 | """ 68 | This ensures that if a user follows a question and then answers that question then he/she 69 | wouldn't get notification 70 | """ 71 | Notification.objects.create( 72 | from_user=from_user, 73 | to_user=follower.user, 74 | msg=msg, 75 | question=question, 76 | is_followed_question=True, 77 | ) 78 | except: 79 | from_user = kwargs.get("instance").from_user 80 | to_user = kwargs.get("instance").to_user 81 | msg = f"{from_user.first_name} {from_user.last_name} started following you" 82 | Notification.objects.create( 83 | from_user=from_user, to_user=to_user, msg=msg, 84 | ) 85 | -------------------------------------------------------------------------------- /notifications/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/notifications/templatetags/__init__.py -------------------------------------------------------------------------------- /notifications/templatetags/notifications.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from notifications.models import Notification 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.simple_tag 8 | def unread_notification(user): 9 | notification_count = Notification.objects.filter( 10 | is_seen=False, to_user=user 11 | ).count() 12 | return notification_count 13 | -------------------------------------------------------------------------------- /notifications/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /notifications/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | app_name = "notifications" 5 | urlpatterns = [ 6 | path("all/", views.NotificationListView.as_view(), name="notifications_all",), 7 | path( 8 | "answers/", 9 | views.NotificationAnswerListView.as_view(), 10 | name="notifications_ans", 11 | ), 12 | path( 13 | "followed-questions/", 14 | views.NotificationFollowedQuestionsListView.as_view(), 15 | name="notifications_followed_questions", 16 | ), 17 | path( 18 | "", views.NotificationDetailView.as_view(), name="notification_detail", 19 | ), 20 | path("dismiss/all", views.dismiss_notification, name="dismiss_all_notification",), 21 | path("dismiss/", views.dismiss_notification, name="dismiss_notification",), 22 | ] 23 | -------------------------------------------------------------------------------- /notifications/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import ListView, DetailView 2 | from django.shortcuts import render 3 | from django.http import JsonResponse 4 | from django.contrib.auth.mixins import LoginRequiredMixin 5 | from django.contrib.auth.decorators import login_required 6 | from .models import Notification 7 | 8 | 9 | class NotificationListView(LoginRequiredMixin, ListView): 10 | model = Notification 11 | login_url = "/users/login/" 12 | 13 | def get_context_data(self, **kwargs): 14 | context = super().get_context_data(**kwargs) 15 | notifications = Notification.objects.filter( 16 | to_user=self.request.user.pk 17 | ).order_by("-created_date") 18 | context["notifications"] = notifications 19 | return context 20 | 21 | 22 | class NotificationDetailView(LoginRequiredMixin, DetailView): 23 | model = Notification 24 | login_url = "/users/login/" 25 | queryset = Notification.objects.all() 26 | 27 | def get_context_data(self, **kwargs): 28 | context = super(NotificationDetailView, self).get_context_data(**kwargs) 29 | obj = self.get_object() 30 | obj.is_seen = True 31 | obj.save() 32 | from_user = obj.from_user 33 | to_user = obj.to_user 34 | question = obj.question 35 | try: 36 | ans = question.answer_set.filter(user=from_user.pk)[0] 37 | context["ans"] = ans 38 | except: 39 | pass 40 | is_requested_question = Notification.objects.filter( 41 | from_user=from_user, 42 | to_user=to_user, 43 | question=question, 44 | is_requested_question=True, 45 | ) 46 | is_following_user_questions = Notification.objects.filter( 47 | from_user=from_user, 48 | to_user=to_user, 49 | question=question, 50 | is_following_user_questions=True, 51 | ) 52 | is_following_user_answers = Notification.objects.filter( 53 | from_user=from_user, 54 | to_user=to_user, 55 | question=question, 56 | is_following_user_answers=True, 57 | ) 58 | context["is_requested_question"] = is_requested_question 59 | context["is_following_user_answers"] = is_following_user_answers 60 | context["is_following_user_questions"] = is_following_user_questions 61 | context["from_user"] = from_user 62 | context["question"] = question 63 | return context 64 | 65 | 66 | class NotificationAnswerListView(LoginRequiredMixin, ListView): 67 | model = Notification 68 | login_url = "/users/login/" 69 | template_name = "notifications/notification_list.html" 70 | 71 | def get_context_data(self, **kwargs): 72 | context = super().get_context_data(**kwargs) 73 | notifications = Notification.objects.filter( 74 | to_user=self.request.user.pk, is_answer=True 75 | ).order_by("-created_date") 76 | context["notifications"] = notifications 77 | return context 78 | 79 | 80 | class NotificationFollowedQuestionsListView(LoginRequiredMixin, ListView): 81 | model = Notification 82 | login_url = "/users/login/" 83 | template_name = "notifications/notification_list.html" 84 | 85 | def get_context_data(self, **kwargs): 86 | context = super().get_context_data(**kwargs) 87 | notifications = Notification.objects.filter( 88 | to_user=self.request.user.pk, is_followed_question=True 89 | ).order_by("-created_date") 90 | context["notifications"] = notifications 91 | return context 92 | 93 | 94 | def dismiss_notification(request, pk=None): 95 | if request.user.is_authenticated: 96 | try: 97 | if pk is None: 98 | """dismiss all""" 99 | Notification.objects.filter(to_user=request.user.pk).delete() 100 | return JsonResponse({"dismiss_all": True, "success": True}) 101 | else: 102 | Notification.objects.get(pk=pk).delete() 103 | return JsonResponse({"success": True}) 104 | except: 105 | return JsonResponse({"success": False}) 106 | -------------------------------------------------------------------------------- /qnA/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/qnA/__init__.py -------------------------------------------------------------------------------- /qnA/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from . import models 3 | 4 | 5 | @admin.register(models.Question) 6 | class CustomQuestionAdmin(admin.ModelAdmin): 7 | list_display = ( 8 | "title", 9 | "user", 10 | "is_anonymous", 11 | "created_date", 12 | "updated_date", 13 | ) 14 | filter_horizontal = ("topics",) 15 | 16 | 17 | @admin.register(models.Answer) 18 | class CustomQuestionAdmin(admin.ModelAdmin): 19 | list_display = ( 20 | "question", 21 | "user", 22 | "is_anonymous", 23 | "created_date", 24 | "updated_date", 25 | ) 26 | 27 | 28 | @admin.register(models.Topic, models.FollowQuestion) 29 | class CustomQuestionAdmin(admin.ModelAdmin): 30 | pass 31 | -------------------------------------------------------------------------------- /qnA/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class QnaConfig(AppConfig): 5 | name = 'qnA' 6 | -------------------------------------------------------------------------------- /qnA/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.forms import ModelForm 3 | from .models import * 4 | from django_summernote.widgets import SummernoteWidget, SummernoteInplaceWidget 5 | 6 | 7 | class QuestionForm(ModelForm): 8 | class Meta: 9 | model = Question 10 | fields = [ 11 | "title", 12 | "is_anonymous", 13 | ] 14 | widgets = { 15 | "title": forms.TextInput( 16 | attrs={ 17 | "class": "form-control input-lg", 18 | "placeholder": "What is your question?", 19 | "style": "height: 70px;", 20 | } 21 | ), 22 | } 23 | labels = {"title": "", "is_anonymous": "Ask Anonymously"} 24 | 25 | 26 | class AnswerForm(ModelForm): 27 | class Meta: 28 | model = Answer 29 | fields = [ 30 | "ans", 31 | "is_anonymous", 32 | ] 33 | widgets = { 34 | "ans": SummernoteWidget(), 35 | } 36 | labels = {"ans": "", "is_anonymous": "Answer Anonymously"} 37 | -------------------------------------------------------------------------------- /qnA/get_topics.py: -------------------------------------------------------------------------------- 1 | import RAKE 2 | 3 | 4 | def get_topics(text): 5 | stop_lists = ( 6 | RAKE.SmartStopList() 7 | + RAKE.FoxStopList() 8 | + RAKE.NLTKStopList() 9 | + RAKE.MySQLStopList() 10 | + RAKE.GoogleSearchStopList() 11 | ) 12 | Rake = RAKE.Rake(stop_lists) 13 | topics = [] 14 | for x, _ in Rake.run(text)[:2]: 15 | words = x.split() 16 | topics.extend(words) 17 | return topics 18 | -------------------------------------------------------------------------------- /qnA/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-24 04:59 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 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Topic', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('topic', models.CharField(max_length=255)), 22 | ], 23 | ), 24 | migrations.CreateModel( 25 | name='Question', 26 | fields=[ 27 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 28 | ('title', models.CharField(max_length=255)), 29 | ('description', models.TextField(max_length=4000)), 30 | ('slug', models.SlugField(max_length=255)), 31 | ('is_anonymous', models.BooleanField(default=False)), 32 | ('created_date', models.DateTimeField(auto_now_add=True)), 33 | ('updated_date', models.DateTimeField(auto_now=True)), 34 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 35 | ], 36 | ), 37 | migrations.CreateModel( 38 | name='Answer', 39 | fields=[ 40 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 41 | ('ans', models.TextField(max_length=4000)), 42 | ('is_anonymous', models.BooleanField(default=False)), 43 | ('created_date', models.DateTimeField(auto_now_add=True)), 44 | ('updated_date', models.DateTimeField(auto_now=True)), 45 | ('topics', models.ManyToManyField(to='qnA.Topic')), 46 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 47 | ], 48 | ), 49 | ] 50 | -------------------------------------------------------------------------------- /qnA/migrations/0002_auto_20200524_1128.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-24 05:58 2 | 3 | import autoslug.fields 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('qnA', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='answer', 16 | name='topics', 17 | ), 18 | migrations.AddField( 19 | model_name='question', 20 | name='topics', 21 | field=models.ManyToManyField(to='qnA.Topic'), 22 | ), 23 | migrations.AlterField( 24 | model_name='question', 25 | name='description', 26 | field=models.TextField(blank=True, max_length=4000), 27 | ), 28 | migrations.AlterField( 29 | model_name='question', 30 | name='slug', 31 | field=autoslug.fields.AutoSlugField(editable=False, populate_from='title', unique=True), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /qnA/migrations/0003_answer_question.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-24 06:10 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('qnA', '0002_auto_20200524_1128'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='answer', 16 | name='question', 17 | field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='qnA.Question'), 18 | preserve_default=False, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /qnA/migrations/0004_auto_20200527_0856.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-27 03:26 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('qnA', '0003_answer_question'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='answer', 15 | options={'ordering': ['-updated_date']}, 16 | ), 17 | migrations.AddField( 18 | model_name='answer', 19 | name='num_vote_down', 20 | field=models.PositiveIntegerField(db_index=True, default=0), 21 | ), 22 | migrations.AddField( 23 | model_name='answer', 24 | name='num_vote_up', 25 | field=models.PositiveIntegerField(db_index=True, default=0), 26 | ), 27 | migrations.AddField( 28 | model_name='answer', 29 | name='vote_score', 30 | field=models.IntegerField(db_index=True, default=0), 31 | ), 32 | migrations.AddField( 33 | model_name='question', 34 | name='num_vote_down', 35 | field=models.PositiveIntegerField(db_index=True, default=0), 36 | ), 37 | migrations.AddField( 38 | model_name='question', 39 | name='num_vote_up', 40 | field=models.PositiveIntegerField(db_index=True, default=0), 41 | ), 42 | migrations.AddField( 43 | model_name='question', 44 | name='vote_score', 45 | field=models.IntegerField(db_index=True, default=0), 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /qnA/migrations/0005_auto_20200527_1754.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-27 12:24 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('qnA', '0004_auto_20200527_0856'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='answer', 15 | options={'ordering': ['-vote_score']}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /qnA/migrations/0006_auto_20200527_2352.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-27 18:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('qnA', '0005_auto_20200527_1754'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='question', 15 | name='topics', 16 | field=models.ManyToManyField(blank=True, to='qnA.Topic'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /qnA/migrations/0007_question_rank.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-10 08:48 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('qnA', '0006_auto_20200527_2352'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='question', 15 | name='rank', 16 | field=models.IntegerField(default=0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /qnA/migrations/0008_auto_20200610_1527.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-10 09:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('qnA', '0007_question_rank'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='question', 15 | name='rank', 16 | field=models.FloatField(default=0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /qnA/migrations/0009_auto_20200611_1454.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-11 09:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('qnA', '0008_auto_20200610_1527'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='answer', 15 | name='ans', 16 | field=models.TextField(), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /qnA/migrations/0010_answer_pin_answer.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-15 14:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('qnA', '0009_auto_20200611_1454'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='answer', 15 | name='pin_answer', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /qnA/migrations/0011_remove_question_description.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-18 17:39 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('qnA', '0010_answer_pin_answer'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='question', 15 | name='description', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /qnA/migrations/0012_followquestion.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-24 07:18 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 | ('qnA', '0011_remove_question_description'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='FollowQuestion', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='follow_question', to='qnA.Question')), 21 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='follow_question_by', to=settings.AUTH_USER_MODEL)), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /qnA/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/qnA/migrations/__init__.py -------------------------------------------------------------------------------- /qnA/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils.text import slugify 3 | from django.urls import reverse 4 | from autoslug import AutoSlugField 5 | from vote.models import VoteModel 6 | from users import models as userModels 7 | 8 | 9 | class Topic(models.Model): 10 | 11 | topic = models.CharField(max_length=255) 12 | 13 | def __str__(self): 14 | return self.topic 15 | 16 | 17 | class Question(VoteModel, models.Model): 18 | title = models.CharField(max_length=255) 19 | slug = AutoSlugField(populate_from="title", unique=True) 20 | user = models.ForeignKey(userModels.User, on_delete=models.CASCADE) 21 | is_anonymous = models.BooleanField(default=False) 22 | created_date = models.DateTimeField(auto_now_add=True) 23 | updated_date = models.DateTimeField(auto_now=True) 24 | topics = models.ManyToManyField(Topic, blank=True) 25 | rank = models.FloatField(default=0) 26 | 27 | def save(self, *args, **kwargs): 28 | self.slug = slugify(self.title) 29 | super(Question, self).save(*args, **kwargs) 30 | 31 | def __str__(self): 32 | return self.title 33 | 34 | # class Meta: 35 | # ordering = ["-vote_score"] 36 | 37 | 38 | class Answer(VoteModel, models.Model): 39 | class Meta: 40 | ordering = ["-vote_score"] 41 | 42 | question = models.ForeignKey(Question, on_delete=models.CASCADE) 43 | ans = models.TextField() 44 | user = models.ForeignKey(userModels.User, on_delete=models.CASCADE) 45 | is_anonymous = models.BooleanField(default=False) 46 | created_date = models.DateTimeField(auto_now_add=True) 47 | updated_date = models.DateTimeField(auto_now=True) 48 | pin_answer = models.BooleanField(default=False) 49 | 50 | def get_absolute_url(self): 51 | return reverse("qnA:question_detail", kwargs={"slug": self.question.slug,},) 52 | 53 | def __str__(self): 54 | return f"{self.user.first_name} on {self.created_date}" 55 | 56 | 57 | class FollowQuestion(models.Model): 58 | question = models.ForeignKey( 59 | Question, on_delete=models.CASCADE, related_name="follow_question", 60 | ) 61 | user = models.ForeignKey( 62 | userModels.User, on_delete=models.CASCADE, related_name="follow_question_by" 63 | ) 64 | -------------------------------------------------------------------------------- /qnA/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /qnA/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | app_name = "qnA" 5 | urlpatterns = [ 6 | path("", views.QnaListView.as_view(), name="home"), 7 | path( 8 | "question//", 9 | views.QuestionDetailView.as_view(), 10 | name="question_detail", 11 | ), 12 | path( 13 | "question/request//", 14 | views.RequestAnswerListView.as_view(), 15 | name="answer_request", 16 | ), 17 | path( 18 | "question/request//", 19 | views.request_answer, 20 | name="answer_request_ajax", 21 | ), 22 | # passing slugs for SEO purpose 23 | path( 24 | "question//answer///", 25 | views.AnswerDetailView.as_view(), 26 | name="answer_detail", 27 | ), 28 | path("ask-question/", views.AskQuestionView.as_view(), name="ask-question"), 29 | path("answer/", views.AnswerView.as_view(), name="answer"), 30 | path("answer/edit/", views.AnswerUpdateView.as_view(), name="edit"), 31 | path("answer/delete/", views.AnswerDeleteView.as_view(), name="delete"), 32 | path( 33 | "question/follow/", 34 | views.follow_question, 35 | name="follow-question", 36 | ), 37 | path("/vote", views.vote, name="question-vote"), 38 | path("question///vote/", views.vote, name="vote"), 39 | path("search/", views.QnaListView.as_view(), name="search"), 40 | path( 41 | "question//request/user/", 42 | views.RequestAnswerListView.as_view(), 43 | name="search-user", 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.2.7 2 | Django==3.0.14 3 | django-autoslug==1.9.7 4 | django-comments-xtd==2.6.1 5 | django-contrib-comments==1.9.2 6 | django-js-asset==1.2.2 7 | django-summernote==0.8.11.6 8 | django-vote==2.2.0 9 | djangorestframework==3.11.2 10 | docutils==0.16 11 | gunicorn==20.0.4 12 | Pillow==8.1.1 13 | python-rake==1.4.5 14 | pytz==2020.1 15 | six==1.15.0 16 | sqlparse==0.3.1 17 | -------------------------------------------------------------------------------- /screenshots/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/screenshots/home.png -------------------------------------------------------------------------------- /static/css/styles.css: -------------------------------------------------------------------------------- 1 | .crim { 2 | font-family: 'Crimson Text', serif; 3 | font-weight: 600; 4 | } 5 | 6 | .card-link { 7 | color: black; 8 | } 9 | 10 | .card-link:hover { 11 | color: #B92B27; 12 | } 13 | 14 | .sticky-top { 15 | top: 0.5em; 16 | } 17 | 18 | .item { 19 | position: relative; 20 | display: inline-block; 21 | } 22 | 23 | .notify-badge { 24 | position: absolute; 25 | right: 10px; 26 | bottom: 10px; 27 | background: #dc3545; 28 | text-align: center; 29 | border-radius: 30px 30px 30px 30px; 30 | color: white; 31 | padding: 5px 10px; 32 | font-size: 15px; 33 | } -------------------------------------------------------------------------------- /static/imgs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/static/imgs/logo.png -------------------------------------------------------------------------------- /static/imgs/no_display_img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/static/imgs/no_display_img.jpg -------------------------------------------------------------------------------- /static/imgs/qnA/briefcase.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/imgs/qnA/building.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/imgs/qnA/caret-down-square-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/imgs/qnA/caret-down-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/imgs/qnA/caret-up-square-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/imgs/qnA/caret-up-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/imgs/qnA/map.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/imgs/qnA/pencil-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Layer 1 -------------------------------------------------------------------------------- /static/imgs/qnA/vectorpaint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Layer 1 -------------------------------------------------------------------------------- /static/js/dismiss_notifications.js: -------------------------------------------------------------------------------- 1 | $('input[name="dismiss"]').click(function (e) { 2 | e.preventDefault(); 3 | var pk = $(this).attr("data-pk"); 4 | if (pk) { 5 | var href = window.location.origin + "/notifications/dismiss/" + pk; 6 | } else { 7 | var href = window.location.origin + "/notifications/dismiss/all"; 8 | } 9 | $.ajax({ 10 | url: href, 11 | success: function (response) { 12 | if (response["success"]) { 13 | if (response["dismiss_all"]) { 14 | $(".dismiss-all").addClass('animate__slideOutRight').delay(1000).queue(function () { $(this).remove(); });; 15 | } 16 | else { 17 | $(".dismiss" + pk).addClass('animate__slideOutRight').delay(1000).queue(function () { $(this).remove(); });; 18 | } 19 | } 20 | }, 21 | error: function (response) { 22 | console.log(response); 23 | } 24 | }); 25 | }); -------------------------------------------------------------------------------- /static/js/follow_toggler.js: -------------------------------------------------------------------------------- 1 | $("#follow").click(function (e) { 2 | e.preventDefault(); 3 | var href = this.href; 4 | console.log(href); 5 | $.ajax({ 6 | url: href, 7 | success: function (response) { 8 | if (response["is_following"]) { 9 | if (response["is_question_follow"]) { 10 | $("#follow").removeClass('badge-danger').addClass('badge-info'); 11 | $("#follow").html("Unfollow Question"); 12 | } 13 | else { 14 | $("#follow").removeClass('btn-danger').addClass('btn-outline-danger'); 15 | $("#follow").html("Unfollow"); 16 | } 17 | } 18 | else { 19 | if (!(response["is_following"])) { 20 | if (response["is_question_follow"]) { 21 | $("#follow").removeClass('badge-info').addClass('badge-danger'); 22 | $("#follow").html("Follow Question"); 23 | } 24 | else { 25 | if (response["is_user_follow"]) { 26 | $("#follow").removeClass('btn-outline-danger').addClass('btn-danger'); 27 | $("#follow").html("Follow"); 28 | } 29 | else { 30 | window.location.href = '/users/login/'; 31 | } 32 | } 33 | } 34 | } 35 | }, 36 | error: function (response) { 37 | console.log(response); 38 | } 39 | }); 40 | }); 41 | $('input[name="follow_list"]').click(function (e) { 42 | e.preventDefault(); 43 | var user_slug = $(this).attr("data-to-user"); 44 | var href = window.location.origin + "/users/follow_toggle/" + user_slug; 45 | $.ajax({ 46 | url: href, 47 | success: function (response) { 48 | if (response["is_following"]) { 49 | $(".follow" + user_slug).removeClass('btn-danger').addClass('btn-outline-danger'); 50 | $(".follow" + user_slug).attr("value", "Unfollow"); 51 | } 52 | else { 53 | if (!(response["is_following"])) { 54 | if (response["is_user_follow"]) { 55 | $(".follow" + user_slug).removeClass('btn-outline-danger').addClass('btn-danger'); 56 | $(".follow" + user_slug).attr("value", "Follow"); 57 | } 58 | else { 59 | window.location.href = '/users/login/'; 60 | } 61 | } 62 | 63 | } 64 | }, 65 | error: function (response) { 66 | console.log(response); 67 | } 68 | }); 69 | }); -------------------------------------------------------------------------------- /static/js/request_answer.js: -------------------------------------------------------------------------------- 1 | $('input[name="request"]').click(function (e) { 2 | e.preventDefault(); 3 | var user_pk = $(this).attr("data-pk"); 4 | var question_pk = $(this).attr("data-qpk"); 5 | var href = window.location.origin + "/question/request/" + question_pk + "/" + user_pk; 6 | $.ajax({ 7 | url: href, 8 | success: function (response) { 9 | if (response["success"]) { 10 | $(".request" + user_pk + question_pk).attr({ 11 | 'disabled': true, 12 | 'value': 'Request Sent', 13 | }); 14 | } 15 | }, 16 | error: function (response) { 17 | console.log(response); 18 | } 19 | }); 20 | }); -------------------------------------------------------------------------------- /static/js/vote.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $(".score-form").each(function (index) { 3 | var is_comment = $(this).attr("data-is-comment"); 4 | var question_pk = $(this).attr("data-pk"); 5 | var is_profile_view = $(this).attr("data-profile"); 6 | var base_url = window.location.origin + "/"; 7 | var upvote_fill_url = base_url + "static/imgs/qnA/caret-up-square-fill.svg"; 8 | var downvote_fill_url = base_url + "static/imgs/qnA/caret-down-square-fill.svg"; 9 | if (is_profile_view) { 10 | var url = base_url + question_pk + "/vote"; 11 | } 12 | else { 13 | var url = question_pk + "/vote"; 14 | } 15 | $.ajax({ 16 | type: 'GET', 17 | url: url, 18 | data: { "status": question_pk, is_comment: is_comment, }, 19 | success: function (response) { 20 | if (response["is_question_detail"]) { 21 | if (is_comment) { 22 | if (response["has_upvoted"]) { 23 | $(".Up_comment" + question_pk).attr('src', upvote_fill_url); 24 | } 25 | if (response["has_downvoted"]) { 26 | $(".Down_comment" + question_pk).attr('src', downvote_fill_url); 27 | } 28 | } 29 | else { 30 | if (response["has_upvoted"]) { 31 | $(".Up" + question_pk).attr('src', upvote_fill_url); 32 | } 33 | if (response["has_downvoted"]) { 34 | $(".Down" + question_pk).attr('src', downvote_fill_url); 35 | } 36 | } 37 | } 38 | else { 39 | if (response["has_upvoted"]) { 40 | $(".Up" + question_pk).attr('src', upvote_fill_url); 41 | } 42 | if (response["has_downvoted"]) { 43 | $(".Down" + question_pk).attr('src', downvote_fill_url); 44 | } 45 | } 46 | } 47 | }); 48 | }); 49 | }); 50 | $('input[name="up"]').click(function (e) { 51 | e.preventDefault(); 52 | var is_comment = $(this).attr("data-is-comment"); 53 | var question_pk = $(this).attr("data-pk"); 54 | var is_profile_view = $(this).attr("data-profile"); 55 | var base_url = window.location.origin + "/"; 56 | var upvote_url = base_url + "static/imgs/qnA/caret-up-square.svg"; 57 | var upvote_fill_url = base_url + "static/imgs/qnA/caret-up-square-fill.svg"; 58 | var downvote_url = base_url + "static/imgs/qnA/caret-down-square.svg"; 59 | if (is_profile_view) { 60 | var url = window.location.origin + "/" + question_pk + "/vote"; 61 | } 62 | else { 63 | var url = question_pk + "/vote"; 64 | } 65 | $.ajax({ 66 | type: 'GET', 67 | url: url, 68 | data: { "up": 'Up', is_comment: is_comment, }, 69 | success: function (response) { 70 | if (response["valid"]) { 71 | if (response["data"]["is_question_detail"]) { 72 | if (is_comment) { 73 | if (response["data"]["has_upvoted"]) { 74 | console 75 | $(".Up_comment" + question_pk).attr('src', upvote_fill_url); 76 | $(".Down_comment" + question_pk).attr('src', downvote_url); 77 | } 78 | else { 79 | console. 80 | $(".Up_comment" + question_pk).attr('src', upvote_url); 81 | } 82 | } 83 | else { 84 | if (response["data"]["has_upvoted"]) { 85 | console 86 | $(".Up" + question_pk).attr('src', upvote_fill_url); 87 | $(".Down" + question_pk).attr('src', downvote_url); 88 | } 89 | else { 90 | console. 91 | $(".Up" + question_pk).attr('src', upvote_url); 92 | } 93 | } 94 | } 95 | else { 96 | if (response["data"]["has_upvoted"]) { 97 | console 98 | $(".Up" + question_pk).attr('src', upvote_fill_url); 99 | $(".Down" + question_pk).attr('src', downvote_url); 100 | } 101 | else { 102 | console. 103 | $(".Up" + question_pk).attr('src', upvote_url); 104 | } 105 | } 106 | if (is_comment) { 107 | $('#score_comment' + question_pk).html(response["data"]["score"]); 108 | } 109 | else { 110 | $('#score' + question_pk).html(response["data"]["score"]); 111 | } 112 | } 113 | else { 114 | window.location.href = '/users/login/'; 115 | } 116 | }, 117 | error: function (response) { 118 | } 119 | }); 120 | }); 121 | $('input[name="down"]').click(function (e) { 122 | e.preventDefault(); 123 | var is_comment = $(this).attr("data-is-comment"); 124 | var question_pk = $(this).attr("data-pk"); 125 | var is_profile_view = $(this).attr("data-profile"); 126 | var base_url = window.location.origin + "/"; 127 | var upvote_url = base_url + "static/imgs/qnA/caret-up-square.svg"; 128 | var downvote_fill_url = base_url + "static/imgs/qnA/caret-down-square-fill.svg"; 129 | var downvote_url = base_url + "static/imgs/qnA/caret-down-square.svg"; 130 | if (is_profile_view) { 131 | var url = window.location.origin + "/" + question_pk + "/vote"; 132 | } 133 | else { 134 | var url = question_pk + "/vote"; 135 | } 136 | $.ajax({ 137 | type: 'GET', 138 | url: url, 139 | data: { "down": 'Down', is_comment: is_comment, }, 140 | success: function (response) { 141 | if (response["valid"]) { 142 | if (response["data"]["is_question_detail"]) { 143 | if (is_comment) { 144 | if (response["data"]["has_downvoted"]) { 145 | console 146 | $(".Down_comment" + question_pk).attr('src', downvote_fill_url); 147 | $(".Up_comment" + question_pk).attr('src', upvote_url); 148 | } 149 | else { 150 | console. 151 | $(".Down_comment" + question_pk).attr('src', downvote_url); 152 | } 153 | } 154 | else { 155 | if (response["data"]["has_downvoted"]) { 156 | console 157 | $(".Down" + question_pk).attr('src', downvote_fill_url); 158 | $(".Up" + question_pk).attr('src', upvote_url); 159 | } 160 | else { 161 | console. 162 | $(".Down" + question_pk).attr('src', downvote_url); 163 | } 164 | } 165 | } 166 | else { 167 | if (response["data"]["has_downvoted"]) { 168 | console 169 | $(".Down" + question_pk).attr('src', downvote_fill_url); 170 | $(".Up" + question_pk).attr('src', upvote_url); 171 | } 172 | else { 173 | console. 174 | $(".Down" + question_pk).attr('src', downvote_url); 175 | } 176 | } 177 | if (is_comment) { 178 | $('#score_comment' + question_pk).html(response["data"]["score"]); 179 | } 180 | else { 181 | $('#score' + question_pk).html(response["data"]["score"]); 182 | } 183 | } 184 | else { 185 | window.location.href = '/users/login/'; 186 | } 187 | }, 188 | error: function (response) { 189 | } 190 | }); 191 | }); -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | {% load notifications %} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% block head %}{% endblock head %} 13 | {% block title %}{% endblock title %} 14 | 15 | 16 | 17 | 55 | {% block content %} 56 | {% endblock %} 57 | 58 | 59 | 60 | {% block scripts %} 61 | {% endblock scripts %} 62 | 63 | 64 | -------------------------------------------------------------------------------- /templates/comments/form.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load comments %} 3 | 4 |
5 | {% csrf_token %} 6 |
7 | 8 | 9 | {% for field in form %} 10 | {% if field.is_hidden %}
{{ field }}
{% endif %} 11 | {% endfor %} 12 | 13 |
{{ form.honeypot }}
14 | 15 |
16 | 19 |
20 | {{ form.title }} 21 |
22 |
23 | 24 |
25 |
26 | {{ form.comment }} 27 |
28 |
29 | 30 | {% if not request.user.is_authenticated %} 31 |
32 | 35 |
36 | {{ form.name }} 37 |
38 |
39 | 40 |
41 | 44 |
45 | {{ form.email }} 46 | {{ form.email.help_text }} 47 |
48 |
49 | 50 |
51 | 54 |
55 | {{ form.url }} 56 |
57 |
58 | {% endif %} 59 | 60 |
61 |
62 |
63 |
64 |
65 | 66 |
67 |
68 | 69 | 70 |
71 |
72 |
-------------------------------------------------------------------------------- /templates/comments/preview.html: -------------------------------------------------------------------------------- 1 | {% extends "django_comments_xtd/base.html" %} 2 | {% load i18n %} 3 | {% load comments_xtd %} 4 | 5 | {% block content %} 6 |

{% trans "Preview your comment:" %}

7 |
8 |
9 |
10 | {% if not comment %} 11 | {% trans "Empty comment." %} 12 | {% else %} 13 | 18 |
19 |
20 | {% now "N j, Y, P" %} -  21 | {% if form.cleaned_data.url %} 22 | {% endif %} 23 | {{ form.cleaned_data.name }} 24 | {% if form.cleaned_data.url %}{% endif %} 25 |
26 | {{ form.cleaned_data.title }}
27 |
{{ comment }}
28 |
29 | {% endif %} 30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {% include "comments/form.html" %} 38 |
39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /templates/django_comments_xtd/comment_tree.html: -------------------------------------------------------------------------------- 1 | {% load l10n %} 2 | {% load i18n %} 3 | {% load comments %} 4 | {% load comments_xtd %} 5 | {% load static %} 6 | 7 | {% for item in comments %} 8 |
9 | 10 | 11 | 13 | 14 |
15 |
16 |
17 |
18 |

19 | 20 | {{ item.comment.name }} 21 | 22 |

23 |

on {{ item.comment.submit_date|localize }}

24 |
25 |
26 |
27 | {% include "includes/django_comments_xtd/comment_content.html" with content=item.comment.comment %} 28 |
29 | {% if item.comment.allow_thread and not item.comment.is_removed %} 30 | 31 | {% if user.is_authenticated %} 32 | {% if allow_feedback %}    {% endif %}{% trans "Reply" %} 34 | {% else %} 35 | Login to Reply 36 | {% endif %} 37 | {% endif %} 38 |

{{item.comment.vote_score}}

39 |
40 | 43 | 46 |
47 |
48 | {% if not item.comment.is_removed and item.children %} 49 | {% render_xtdcomment_tree with comments=item.children %} 50 | {% endif %} 51 |
52 |
53 | {% endfor %} -------------------------------------------------------------------------------- /templates/django_comments_xtd/email_confirmation_request.html: -------------------------------------------------------------------------------- 1 |

{{ comment.user_name }},

2 | 3 |

You or someone in behalf of you have requested to post a comment into this page:
4 | http://{{ site.domain }}{{ comment.content_object.get_absolute_url }} 5 |

6 | 7 | Comment title: {{ comment.title }}
8 | 9 |

The comment:
10 | {{ comment.comment }} 11 |


12 | 13 |

If you do not wish to post the comment, please ignore this message or report an incident to {{ contact }}. Otherwise click on the link below to confirm the comment.

14 | 15 |

http://{{ site.domain }}{{ confirmation_url|slice:":40" }}...

16 | 17 |

If clicking does not work, you can also copy and paste the address into your browser's address window.

18 | 19 |

Thanks for your comment!
20 | --
21 | Kind regards,
22 | {{ site }}

23 | -------------------------------------------------------------------------------- /templates/django_comments_xtd/email_confirmation_request.txt: -------------------------------------------------------------------------------- 1 | {{ comment.user_name }}, 2 | 3 | You or someone in behalf of you have requested to post a comment to the following URL. 4 | 5 | URL: http://{{ site.domain }}{{ comment.content_object.get_absolute_url }} 6 | 7 | --- Comment title: --- 8 | {{ comment.title }} 9 | 10 | --- Comment: --- 11 | {{ comment.comment }} 12 | ---------------- 13 | 14 | If you do not wish to post the comment, please ignore this message or report an incident to {{ contact|safe }}. Otherwise click on the link below to confirm the comment. 15 | 16 | http://{{ site.domain }}{{ confirmation_url }} 17 | 18 | If clicking does not work, you can also copy and paste the address into your browser's address window. 19 | Thanks for your comment! 20 | 21 | -- 22 | Kind regards, 23 | {{ site }} 24 | -------------------------------------------------------------------------------- /templates/django_comments_xtd/reply.html: -------------------------------------------------------------------------------- 1 | {% extends "django_comments_xtd/base.html" %} 2 | {% load i18n %} 3 | {% load comments %} 4 | {% load comments_xtd %} 5 | 6 | {% block title %}{% trans "Comment reply" %}{% endblock %} 7 | 8 | {% block header %} 9 | {{ comment.content_object }} 10 | {% endblock %} 11 | 12 | {% block content %} 13 |
{% trans "Reply to comment" %}
14 |
15 |
16 |
17 |
18 |
19 | {% if comment.user_url %} 20 | 21 | {{ comment.user_email|xtd_comment_gravatar }} 22 | 23 | {% else %} 24 | {{ comment.user_email|xtd_comment_gravatar }} 25 | {% endif %} 26 |
27 |
28 |
29 | {{ comment.submit_date|date:"N j, Y, P" }} -  30 | {% if comment.user_url %} 31 | {% endif %} 32 | {{ comment.user_name }}{% if comment.user_url %}{% endif %} 33 |
34 | {{ comment.title }}
35 |
{{ comment.comment }}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | {% include "comments/form.html" %} 45 |
46 | {% endblock %} -------------------------------------------------------------------------------- /templates/notifications/base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | {% block content %} 4 | 5 |
6 |
7 |
8 |
9 |
Your Notifications
10 |
11 |
All 12 | Notifications
13 |
Answers
15 |
Followed 17 | Questions
18 |
Questions Requests
19 |
20 |
21 |
22 | Made With ❤ By Pratik 23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 |
33 | {% block notification %}{% endblock notification %} 34 |
35 |
36 |
37 | {% endblock content %} -------------------------------------------------------------------------------- /templates/notifications/notification_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "notifications/base.html" %} 2 | {% load static %} 3 | {% block title %}Notification Detail{% endblock title %} 4 | {% block notification %} 5 |
6 |
7 |
8 |
9 |
10 |
11 | 13 |
14 |
15 |
16 | 17 | 18 | {{question.user.first_name | title}} 19 | {{question.user.last_name | title}} 20 | 21 | | 22 | {{question.created_date |date:'d F, Y'}} 23 |
24 |
{{question.user.bio}}
25 |
26 |
27 |
28 |
29 | 43 | 78 |
79 |
80 | {% endblock notification %} -------------------------------------------------------------------------------- /templates/notifications/notification_list.html: -------------------------------------------------------------------------------- 1 | {% extends "notifications/base.html" %} 2 | {% load static %} 3 | {% block head %} 4 | 5 | {% endblock head %} 6 | {% block title %}Notifications{% endblock title %} 7 | {% block notification %} 8 | {% if notifications %} 9 |
10 | {% for notification in notifications %} 11 |
13 |
14 |
15 |
16 |
17 | {% if not notification.is_anonymous %} 18 | 19 | 21 | 22 | {% else %} 23 | 24 | {% endif %} 25 |
26 | {% if notification.question %} 27 | 29 | {% else %} 30 | 32 | {% endif %} 33 |
34 |
35 | 36 | {{notification.msg}} 37 | 38 |
39 |
40 |
41 |
42 |
43 | 45 |
46 |
47 |
48 |
49 | {% endfor %} 50 |
51 | {% else %} 52 |

No Notifications

53 | {% endif %} 54 | 55 | 56 | {% if is_paginated %} 57 |
58 |
    59 | {% if page_obj.has_previous %} 60 |
  • 61 | 62 | 63 | Previous 64 | 65 |
  • 66 | {% endif %} 67 | 68 |
  • 69 | Page {{ page_obj.number }} of 70 | {{ page_obj.paginator.num_pages }} 71 | 72 |
  • 73 | {% if page_obj.has_next %} 74 |
  • 75 | 76 | 77 | Next 78 | 79 |
  • 80 | {% endif %} 81 |
82 |
83 | {% endif %} 84 | {% endblock notification %} 85 | 86 | {% block scripts %} 87 | 88 | {% endblock scripts %} -------------------------------------------------------------------------------- /templates/qnA/answer_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "users/login_and_register.html" %} 2 | {% block title %}Are You Sure You Want to Delete?{% endblock title %} 3 | {% block img %} 4 | {% endblock img %} 5 | {% block form %} 6 |
7 | {% csrf_token %} 8 |
Are you sure you want to delete your Answer?
9 | {% block btn %} 10 | 11 | No 12 | {% endblock btn %} 13 |
14 | {% endblock form %} 15 | {% block msg %}{% endblock msg %} 16 | {% block footer %} {% endblock footer %} -------------------------------------------------------------------------------- /templates/qnA/answer_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | {% load i18n %} 4 | {% load comments %} 5 | {% load comments_xtd %} 6 | {% block title %}{{question}}{% endblock title %} 7 | {% block content %} 8 |
9 |
10 |
11 | 29 |
30 |
31 |
32 |

{{user_answer.question}}

33 |
Answer 35 | Answer | 36 | {{user_answer.user.first_name}} {{user_answer.user.last_name}}'s Answer 37 |
38 |
39 |
40 |
41 |
42 |
43 | {{user_answer.ans | safe}} 44 |
45 |
46 | 111 |
112 |
113 |
114 |
115 |
116 | 117 | 118 | {% endblock %} -------------------------------------------------------------------------------- /templates/qnA/answer_request.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | {% block title %}Request Answers{% endblock title %} 4 | {% block content %} 5 |
6 |
7 |
8 |
9 |
10 |
Request Answers:
11 |

{{question}}

12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 21 | 22 |
23 |
24 |
25 |
26 | {% block profile %} 27 | {% if suggested_users %} 28 |
Suggested Users
29 | {% for suggested_user,is_requested_question in suggested_users.items %} 30 | 31 | {% if suggested_user != request.user %} 32 |
33 |
34 |
35 |
36 |
37 | 39 |
40 |
41 | 49 |
{{suggested_user.bio}}
50 |
51 |
52 |
53 | 54 | {% if is_requested_question %} 55 | 58 | {% else %} 59 | 62 | {% endif %} 63 | 64 |
65 |
66 |
67 |
68 | {% endif %} 69 | {% endfor %} 70 | {% else %} 71 |
Search Results
72 | {% if user_l %} 73 | {% for user,is_requested_question in user_l.items %} 74 | 75 | {% if user != request.user %} 76 |
77 |
78 |
79 |
80 |
81 | 83 |
84 |
85 | 93 |
{{user.bio}}
94 |
95 |
96 |
97 | 98 | {% if is_requested_question %} 99 | 102 | {% else %} 103 | 106 | {% endif %} 107 | 108 |
109 |
110 |
111 |
112 | {% endif %} 113 | {% endfor %} 114 | {% else %} 115 |
No such user found
116 | {% endif %} 117 | {% endif %} 118 | {% endblock profile %} 119 |
120 |
121 |
122 | {% endblock content %} 123 | {% block scripts %} 124 | 125 | {% endblock scripts %} -------------------------------------------------------------------------------- /templates/qnA/qnA.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | {% block title %} 4 | {% if question %} 5 | Answer {{question}} 6 | {% else %} 7 | Ask Question 8 | {% endif %} 9 | {% endblock title %} 10 | {% block content %} 11 | {% if messages %} 12 | {% for message in messages %} 13 | 19 | {% endfor %} 20 | {% endif %} 21 |
22 |
23 |
24 |
25 | {% if user.is_authenticated %} 26 |
27 | 29 | 31 |
{{user.bio}}
32 | Edit Profile 33 |
34 | {% endif %} 35 |
36 |
37 |
38 | Made With ❤ By Pratik 39 |
40 |
41 |
42 |
43 | 44 | {% if question %} 45 |

{{question}}

46 |
47 |
48 | {% csrf_token %} 49 | {{ form.as_p }} 50 | 51 |
52 | {% else %} 53 |
54 |
55 |
56 | {% csrf_token %} 57 | {{ form.as_p }} 58 | 59 |
60 |
61 |
62 | {% endif %} 63 |
64 |
65 |
66 | {% endblock content %} -------------------------------------------------------------------------------- /templates/qnA/qnA_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | {% block title %}Home{% endblock title %} 4 | {% block content %} 5 | {% if messages %} 6 | {% for message in messages %} 7 | 13 | {% endfor %} 14 | {% endif %} 15 |
16 |
17 |
18 |
19 | {% if user.is_authenticated %} 20 |
21 | 23 | 29 |
{{user.bio | title}}
30 | Edit Profile 31 |
32 | {% endif %} 33 |
34 |
35 |
36 | Made With ❤ By Pratik 37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 |
45 | 46 |

What is your question?

47 |
48 |
49 |
50 |
51 |
52 | {% for question in questions %} 53 |
54 |
55 |
56 |
57 |
58 |
59 | 60 | {% if not question.is_anonymous %} 61 | 62 | 64 | 65 | {% else %} 66 | 68 | {% endif %} 69 | 70 |
71 |
72 |
73 | {% if not question.is_anonymous %} 74 | 75 | 77 | {{question.user.first_name | title }} 78 | {{question.user.last_name | title }} 79 | 80 | | 81 | {% else %} 82 | Anonymous | 83 | {% endif %} 84 | {{question.created_date |date:'d F, Y'}} 85 |
86 | {% if not question.is_anonymous %} 87 |
{{question.user.bio}}
88 | {% endif %} 89 |
90 |
91 |
92 |

{{question.vote_score}}

93 |
94 | 97 | 100 |
101 |
102 |
103 |
104 | 121 | {% if question.answer_set.all.0.ans %} 122 | 165 | {% else %} 166 | 176 | {% endif %} 177 |
178 |
179 | {% endfor %} 180 |
181 | {% if is_paginated %} 182 |
183 |
    184 | {% if page_obj.has_previous %} 185 |
  • 186 | 187 | 188 | Previous 189 | 190 |
  • 191 | {% endif %} 192 | 193 |
  • 194 | Page {{ page_obj.number }} of 195 | {{ page_obj.paginator.num_pages }} 196 | 197 |
  • 198 | {% if page_obj.has_next %} 199 |
  • 200 | 201 | 202 | Next 203 | 204 |
  • 205 | {% endif %} 206 |
207 |
208 | {% endif %} 209 |
210 |
211 |
212 | {% endblock content %} 213 | {% block scripts %} 214 | 215 | {% endblock scripts %} -------------------------------------------------------------------------------- /templates/qnA/question_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | {% load i18n %} 4 | {% load comments %} 5 | {% load comments_xtd %} 6 | {% block title %}{{question.title}}{% endblock title %} 7 | {% block content %} 8 |
9 |
10 |
11 | 29 |
30 |
31 |
32 |

{{question}}

33 |
34 | {{question.answer_set.count}} Answers | 35 | {% if not is_answered %} 36 | Answer 38 | Answer 39 | {% else %} 40 | Edit 42 | Edit Answer | 43 | Delete 45 | Delete Answer 46 | {% endif %} 47 | | 48 | {% if question.user != user %} 49 | {% if is_following %} 50 | 51 | Unfollow Question 52 | {% else %} 53 | 54 | Follow Question 55 | {% endif %} | 56 | {% endif %} 57 | 58 | Request Answer 59 |
60 |
61 |
62 | {% if question.answer_set.count == 0 %} 63 |

No Answers

64 | {% else %} 65 | {% for answer in question.answer_set.all %} 66 |
67 |
68 |
69 |
70 |
71 | {% if not answer.is_anonymous %} 72 | 73 | 75 | 76 | {% else %} 77 | 79 | {% endif %} 80 |
81 |
82 |
83 | {% if answer.is_anonymous %} 84 | Anonymous 85 | {% else %} 86 | 87 | 89 | {{answer.user.first_name | title}} {{answer.user.last_name | title}} 90 | 91 | 92 | {% endif %} 93 | | 94 | {{answer.created_date |date:'d F, Y'}} 95 | {% if answer.created_date != answer.updated_date %} 96 | (Edited) 97 | {% endif %} 98 |
99 | {% if not answer.is_anonymous %} 100 |
{{answer.user.bio}}
101 | {% endif %} 102 |
103 |
104 |
105 |
106 |
107 |
108 | {{answer.ans | safe}} 109 |
110 |
111 | {% if question.answer_set.all.0.ans %} 112 | 171 | {% else %} 172 | 181 | {% endif %} 182 |
183 | {% endfor %} 184 | {% endif %} 185 |
186 |
187 |
188 |
189 | {% endblock %} 190 | {% block scripts %} 191 | 192 | 193 | {% endblock scripts %} -------------------------------------------------------------------------------- /templates/users/base_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | {% block content %} 4 |
5 |
6 |
7 |
8 |
9 | {% block edit_form %}{% endblock edit_form %} 10 |
11 |
12 |

Made With ❤ By 13 | Pratik 14 |

15 |
16 |
17 |
18 | {% endblock content %} -------------------------------------------------------------------------------- /templates/users/edit.html: -------------------------------------------------------------------------------- 1 | {% extends "users/base_form.html" %} 2 | {% load static %} 3 | {% block title %} 4 | {% if is_profile_update %} 5 | Edit Profile 6 | {% else %} 7 | Edit Credentials and Highlights 8 | {% endif %} 9 | {% endblock title %} 10 | {% block edit_form %} 11 | {% if is_profile_update %} 12 | 14 | {% endif %} 15 |
16 | {% csrf_token %} 17 | {{form.as_p}} 18 | {% if is_profile_update %} 19 | 20 | {% else %} 21 | 22 | {% endif %} 23 |
24 |

25 | {% endblock edit_form %} -------------------------------------------------------------------------------- /templates/users/login.html: -------------------------------------------------------------------------------- 1 | {% extends "users/login_and_register.html" %} 2 | {% block title %}Login{% endblock title %} 3 | {% block btn %} 4 | 5 | {% endblock btn %} 6 | {% block msg %} 7 |

Don't have an account? Register 8 |

9 | {% endblock msg %} -------------------------------------------------------------------------------- /templates/users/login_and_register.html: -------------------------------------------------------------------------------- 1 | {% extends "users/base_form.html" %} 2 | {% load static %} 3 | {% block edit_form %} 4 | {% block img %} 5 | 6 | {% endblock img %} 7 | {% block form %} 8 |
9 | {% csrf_token %} 10 | {{form.as_p}} 11 | {% block btn %}{% endblock btn %} 12 |
13 | {% endblock form %} 14 | {% block msg %}{% endblock msg %} 15 | {% endblock edit_form %} -------------------------------------------------------------------------------- /templates/users/register.html: -------------------------------------------------------------------------------- 1 | {% extends "users/login_and_register.html" %} 2 | {% block title %}Register{% endblock title %} 3 | {% block btn %} 4 | 5 | {% endblock btn %} 6 | {% block msg %} 7 |

Already have an account? Login 8 |

9 | {% endblock msg %} -------------------------------------------------------------------------------- /templates/users/user_answers.html: -------------------------------------------------------------------------------- 1 | {% extends "users/user_detail.html" %} 2 | {% load static %} 3 | {% block profile %} 4 | 215 | {% endblock profile %} -------------------------------------------------------------------------------- /templates/users/user_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | {% block title %}{{user.first_name | title}} {{user.last_name | title}}'s Profile{% endblock title %} 4 | {% block content %} 5 |
6 |
7 |
8 |
9 |
10 | 12 |
13 |
14 |

{{user.first_name | title}} {{user.last_name | title}}

15 |
{{user.bio}}
16 |
17 |
18 |
19 | {% if request.user != user %} 20 | {% if is_following %} 21 | Unfollow 23 | {% else %} 24 | Follow 26 | {% endif %} 27 | {% endif %} 28 |
29 | 30 |
31 |
32 | 55 |
56 | {% block profile %} 57 | {% if answers %} 58 |

{{user.first_name | title}} {{user.last_name | title}}'s Pinned Answers 59 |

60 | 147 | {% else %} 148 |

{{user.first_name | title}} {{user.last_name | title}} has not pinned any 149 | answers. 150 |

151 | {% endif %} 152 | 153 | {% endblock profile %} 154 |
155 |
156 |
157 |
Credentials & Highlights
158 |
159 |
160 |
161 | {% if user.workplace_set.all %} 162 | Works at 163 | {{user.workplace_set.all.0.position}} at {{user.workplace_set.all.0.company_name}} 164 | {% if user.workplace_set.all.0.start_year %} 165 | 166 | {% if user.workplace_set.all.0.is_currently_working or not user.workplace_set.all.0.end_year %} 167 | ({{user.workplace_set.all.0.start_year}} - Present) 168 | {% else %} 169 | ({{user.workplace_set.all.0.start_year}} - {{user.workplace_set.all.0.end_year}}) 170 | {% endif %} 171 | 172 | {% endif %} 173 | {% if user == request.user %} 174 | 175 | Edit 176 | Remove 177 | 178 | {% endif %} 179 | {% elif user == request.user %} 180 | Works at 181 | 182 | Add 183 | Workplace 184 | 185 | {% endif %} 186 |
187 |
188 | {% if user.education_set.all %} 189 | Works at 190 | Studied At {{user.education_set.all.0.school_name}} 191 | {% if user.education_set.all.0.start_year %} 192 | 193 | {% if user.education_set.all.0.is_currently_studying or not user.education_set.all.0.end_year %} 194 | ({{user.education_set.all.0.start_year}} - Present) 195 | {% else %} 196 | ({{user.education_set.all.0.start_year}} - {{user.education_set.all.0.end_year}}) 197 | {% endif %} 198 | 199 | {% endif %} 200 | {% if user == request.user %} 201 | 202 | Edit 203 | Remove 204 | 205 | {% endif %} 206 | {% elif user == request.user %} 207 | Works at 208 | 209 | Add 210 | Education 211 | 212 | {% endif %} 213 |
214 |
215 | {% if user.location %} 216 | Lives at 217 | Lives in {{user.location}} 218 | {% if user == request.user %} 219 | 220 | Edit 221 | Remove 222 | 223 | {% endif %} 224 | {% elif user == request.user %} 225 | lives at 226 | 227 | Add 228 | Location 229 | 230 | {% endif %} 231 |
232 |
233 |
234 |
235 | {% endblock content %} 236 | {% block scripts %} 237 | 238 | 239 | {% endblock scripts %} -------------------------------------------------------------------------------- /templates/users/user_followers.html: -------------------------------------------------------------------------------- 1 | {% extends "users/user_detail.html" %} 2 | {% load is_following %} 3 | {% load static %} 4 | {% block profile %} 5 | 55 | {% endblock profile %} -------------------------------------------------------------------------------- /templates/users/user_following.html: -------------------------------------------------------------------------------- 1 | {% extends "users/user_detail.html" %} 2 | {% load static %} 3 | {% block profile %} 4 | 47 | {% endblock profile %} -------------------------------------------------------------------------------- /templates/users/user_questions.html: -------------------------------------------------------------------------------- 1 | {% extends "users/user_detail.html" %} 2 | {% load static %} 3 | {% block profile %} 4 | 215 | {% endblock profile %} -------------------------------------------------------------------------------- /users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/users/__init__.py -------------------------------------------------------------------------------- /users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | from . import models 4 | 5 | 6 | @admin.register(models.User) 7 | class CustomUserAdmin(UserAdmin): 8 | list_display = ( 9 | "username", 10 | "first_name", 11 | "last_name", 12 | "email", 13 | "is_active", 14 | "is_staff", 15 | "is_superuser", 16 | "slug", 17 | ) 18 | fieldsets = UserAdmin.fieldsets + (("Bio", {"fields": ("bio", "location"),}),) 19 | 20 | 21 | @admin.register(models.Follow, models.Education, models.WorkPlace) 22 | class CustomFollowAdmin(admin.ModelAdmin): 23 | pass 24 | -------------------------------------------------------------------------------- /users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | name = 'users' 6 | -------------------------------------------------------------------------------- /users/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.contrib.auth.password_validation import validate_password 3 | from django.core import validators 4 | from django import forms 5 | from .models import User, WorkPlace, Education 6 | 7 | 8 | class LoginForm(forms.Form): 9 | email = forms.EmailField( 10 | label="Enter Email", 11 | required=True, 12 | widget=forms.TextInput( 13 | attrs={"class": "form-control", "placeholder": "Enter Email"} 14 | ), 15 | ) 16 | password = forms.CharField( 17 | label="Enter password", 18 | widget=forms.PasswordInput( 19 | attrs={"class": "form-control", "placeholder": "Enter Password"} 20 | ), 21 | ) 22 | 23 | def clean(self): 24 | email = self.cleaned_data.get("email") 25 | password = self.cleaned_data.get("password") 26 | try: 27 | user = User.objects.get(username=email) 28 | if user.check_password(password): 29 | return self.cleaned_data 30 | else: 31 | self.add_error("password", forms.ValidationError("Password Incorrect")) 32 | except: 33 | self.add_error("email", forms.ValidationError("User Doesn't Exist")) 34 | 35 | return email 36 | 37 | def save(self): 38 | email = self.cleaned_data.get("email") 39 | password = self.cleaned_data.get("password") 40 | 41 | 42 | class RegisterForm(forms.Form): 43 | first_name = forms.CharField( 44 | max_length=30, 45 | required=True, 46 | widget=forms.TextInput( 47 | attrs={"class": "form-control", "placeholder": "Enter First Name"} 48 | ), 49 | ) 50 | last_name = forms.CharField( 51 | max_length=30, 52 | required=True, 53 | widget=forms.TextInput( 54 | attrs={"class": "form-control", "placeholder": "Enter Last Name"} 55 | ), 56 | ) 57 | email = forms.EmailField( 58 | label="Enter email", 59 | required=True, 60 | widget=forms.TextInput( 61 | attrs={"class": "form-control", "placeholder": "Enter Email"} 62 | ), 63 | ) 64 | password = forms.CharField( 65 | label="Enter password", 66 | widget=forms.PasswordInput( 67 | attrs={"class": "form-control", "placeholder": "Enter Password"} 68 | ), 69 | validators=[validate_password], 70 | ) 71 | password1 = forms.CharField( 72 | label="Confirm password", 73 | widget=forms.PasswordInput( 74 | attrs={"class": "form-control", "placeholder": "Confirm Password"} 75 | ), 76 | validators=[validate_password], 77 | ) 78 | 79 | def clean_email(self): 80 | email = self.cleaned_data.get("email") 81 | try: 82 | User.objects.get(email=email) 83 | raise forms.ValidationError("User Already exists") 84 | except User.DoesNotExist: 85 | return email 86 | 87 | def clean_password1(self): 88 | password1 = self.cleaned_data.get("password1") 89 | password = self.cleaned_data.get("password") 90 | if password1 != password: 91 | raise forms.ValidationError( 92 | "Password and Confirmation Password does not match." 93 | ) 94 | else: 95 | return password1 96 | 97 | def save(self): 98 | first_name = self.cleaned_data.get("first_name") 99 | last_name = self.cleaned_data.get("last_name") 100 | email = self.cleaned_data.get("email") 101 | password = self.cleaned_data.get("password") 102 | createdUser = User.objects.create_user(email, email, password) 103 | createdUser.first_name = first_name 104 | createdUser.last_name = last_name 105 | createdUser.save() 106 | 107 | 108 | class ProfileForm(forms.ModelForm): 109 | class Meta: 110 | model = User 111 | fields = ( 112 | "first_name", 113 | "last_name", 114 | "bio", 115 | "location", 116 | "display_img", 117 | ) 118 | widgets = { 119 | "first_name": forms.TextInput( 120 | attrs={"class": "form-control", "placeholder": "Enter First Name"} 121 | ), 122 | "last_name": forms.TextInput( 123 | attrs={"class": "form-control", "placeholder": "Enter Last Name"} 124 | ), 125 | "location": forms.TextInput( 126 | attrs={"class": "form-control", "placeholder": "Enter Location"} 127 | ), 128 | "bio": forms.Textarea( 129 | attrs={"class": "form-control", "placeholder": "Enter Bio", "rows": 3,} 130 | ), 131 | } 132 | 133 | 134 | class WorkPlaceForm(forms.ModelForm): 135 | class Meta: 136 | model = WorkPlace 137 | fields = ( 138 | "company_name", 139 | "position", 140 | "start_year", 141 | "end_year", 142 | "is_currently_working", 143 | ) 144 | widgets = { 145 | "company_name": forms.TextInput( 146 | attrs={"class": "form-control", "placeholder": "Enter Company Name"} 147 | ), 148 | "position": forms.TextInput( 149 | attrs={"class": "form-control", "placeholder": "Enter Your Position"} 150 | ), 151 | "start_year": forms.Select(attrs={"class": "form-control",}), 152 | "end_year": forms.Select(attrs={"class": "form-control",}), 153 | } 154 | labels = { 155 | "is_currently_working": "Currently Working", 156 | } 157 | 158 | 159 | class EducationForm(forms.ModelForm): 160 | class Meta: 161 | model = Education 162 | fields = ( 163 | "school_name", 164 | "start_year", 165 | "end_year", 166 | "is_currently_studying", 167 | ) 168 | widgets = { 169 | "school_name": forms.TextInput( 170 | attrs={"class": "form-control", "placeholder": "Enter School Name"} 171 | ), 172 | "start_year": forms.Select(attrs={"class": "form-control",}), 173 | "end_year": forms.Select(attrs={"class": "form-control",}), 174 | } 175 | labels = { 176 | "is_currently_studying": "Currently Studying", 177 | } 178 | -------------------------------------------------------------------------------- /users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-05-24 04:02 2 | 3 | import django.contrib.auth.models 4 | import django.contrib.auth.validators 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('auth', '0011_update_proxy_permissions'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='User', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('password', models.CharField(max_length=128, verbose_name='password')), 23 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 24 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 25 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), 26 | ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), 27 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 28 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 29 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 30 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 31 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 32 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 33 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), 34 | ], 35 | options={ 36 | 'verbose_name': 'user', 37 | 'verbose_name_plural': 'users', 38 | 'abstract': False, 39 | }, 40 | managers=[ 41 | ('objects', django.contrib.auth.models.UserManager()), 42 | ], 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /users/migrations/0002_user_bio.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-11 08:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='bio', 16 | field=models.TextField(default=''), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /users/migrations/0003_user_display_img.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-13 12:27 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0002_user_bio'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='display_img', 16 | field=models.ImageField(blank=True, upload_to='users'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /users/migrations/0004_user_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-13 17:02 2 | 3 | import autoslug.fields 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0003_user_display_img'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='user', 16 | name='slug', 17 | field=autoslug.fields.AutoSlugField(default='hi', editable=False, populate_from=['first_name', 'last_name']), 18 | preserve_default=False, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /users/migrations/0005_remove_user_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-13 17:06 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0004_user_slug'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='user', 15 | name='slug', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /users/migrations/0006_user_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-13 17:08 2 | 3 | import autoslug.fields 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0005_remove_user_slug'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='user', 16 | name='slug', 17 | field=autoslug.fields.AutoSlugField(default='super-user', editable=False, populate_from=['first_name', 'last_name']), 18 | preserve_default=False, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /users/migrations/0007_auto_20200613_2329.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-13 17:59 2 | 3 | import autoslug.fields 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0006_user_slug'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='user', 16 | name='slug', 17 | field=autoslug.fields.AutoSlugField(editable=False, populate_from='first_name'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /users/migrations/0008_auto_20200613_2337.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-13 18:07 2 | 3 | import autoslug.fields 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0007_auto_20200613_2329'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='user', 16 | name='slug', 17 | field=autoslug.fields.AutoSlugField(editable=False, populate_from='first_name', unique=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /users/migrations/0009_auto_20200616_0030.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-15 19:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0008_auto_20200613_2337'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='followers', 16 | field=models.PositiveIntegerField(default=0), 17 | ), 18 | migrations.AddField( 19 | model_name='user', 20 | name='following', 21 | field=models.PositiveIntegerField(default=0), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /users/migrations/0010_follow.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-15 19:05 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 | ('users', '0009_auto_20200616_0030'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Follow', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('from_person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='from_people', to=settings.AUTH_USER_MODEL)), 20 | ('to_person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_people', to=settings.AUTH_USER_MODEL)), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /users/migrations/0011_auto_20200616_0057.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-15 19:27 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0010_follow'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='follow', 15 | old_name='from_person', 16 | new_name='from_user', 17 | ), 18 | migrations.RenameField( 19 | model_name='follow', 20 | old_name='to_person', 21 | new_name='to_user', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /users/migrations/0012_auto_20200616_2023.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-16 14:53 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0011_auto_20200616_0057'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='user', 15 | name='followers', 16 | ), 17 | migrations.RemoveField( 18 | model_name='user', 19 | name='following', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /users/migrations/0013_auto_20200703_2257.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-07-03 17:27 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0012_auto_20200616_2023'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Education', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('school_name', models.CharField(max_length=100)), 19 | ('start_year', models.IntegerField(blank=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016), (2017, 2017), (2018, 2018), (2019, 2019), (2020, 2020)], max_length=4, null=True)), 20 | ('end_year', models.IntegerField(blank=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016), (2017, 2017), (2018, 2018), (2019, 2019), (2020, 2020)], max_length=4, null=True)), 21 | ('is_currently_studying', models.BooleanField(default=False)), 22 | ], 23 | ), 24 | migrations.CreateModel( 25 | name='WorkPlace', 26 | fields=[ 27 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 28 | ('company_name', models.CharField(max_length=100)), 29 | ('position', models.CharField(max_length=100)), 30 | ('start_year', models.IntegerField(blank=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016), (2017, 2017), (2018, 2018), (2019, 2019), (2020, 2020)], max_length=4, null=True)), 31 | ('end_year', models.IntegerField(blank=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016), (2017, 2017), (2018, 2018), (2019, 2019), (2020, 2020)], max_length=4, null=True)), 32 | ('is_currently_working', models.BooleanField(default=False)), 33 | ], 34 | ), 35 | migrations.AddField( 36 | model_name='user', 37 | name='location', 38 | field=models.CharField(blank=True, max_length=100), 39 | ), 40 | migrations.AddField( 41 | model_name='user', 42 | name='education', 43 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='education', to='users.Education'), 44 | ), 45 | migrations.AddField( 46 | model_name='user', 47 | name='work_place', 48 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='work_place', to='users.WorkPlace'), 49 | ), 50 | ] 51 | -------------------------------------------------------------------------------- /users/migrations/0014_auto_20200703_2303.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-07-03 17:33 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 | ('users', '0013_auto_20200703_2257'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='user', 17 | name='education', 18 | ), 19 | migrations.RemoveField( 20 | model_name='user', 21 | name='work_place', 22 | ), 23 | migrations.AddField( 24 | model_name='education', 25 | name='user', 26 | field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 27 | preserve_default=False, 28 | ), 29 | migrations.AddField( 30 | model_name='workplace', 31 | name='user', 32 | field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 33 | preserve_default=False, 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /users/migrations/0015_auto_20200704_0459.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-07-03 23:29 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0014_auto_20200703_2303'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='education', 15 | name='end_year', 16 | field=models.IntegerField(blank=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016), (2017, 2017), (2018, 2018), (2019, 2019), (2020, 2020)], null=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='education', 20 | name='start_year', 21 | field=models.IntegerField(blank=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016), (2017, 2017), (2018, 2018), (2019, 2019), (2020, 2020)], null=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='workplace', 25 | name='end_year', 26 | field=models.IntegerField(blank=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016), (2017, 2017), (2018, 2018), (2019, 2019), (2020, 2020)], null=True), 27 | ), 28 | migrations.AlterField( 29 | model_name='workplace', 30 | name='start_year', 31 | field=models.IntegerField(blank=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016), (2017, 2017), (2018, 2018), (2019, 2019), (2020, 2020)], null=True), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/users/migrations/__init__.py -------------------------------------------------------------------------------- /users/mixins.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.mixins import UserPassesTestMixin 2 | from django.shortcuts import redirect 3 | 4 | 5 | class LogoutRequiredMixin(UserPassesTestMixin): 6 | def test_func(self): 7 | return not self.request.user.is_authenticated 8 | 9 | def handle_no_permission(self): 10 | return redirect("qnA:home") 11 | -------------------------------------------------------------------------------- /users/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.db import models 3 | from django.contrib.auth.models import AbstractUser 4 | from django.utils.text import slugify 5 | from autoslug import AutoSlugField 6 | 7 | 8 | class User(AbstractUser): 9 | bio = models.TextField(default="") 10 | display_img = models.ImageField(blank=True, upload_to="users") 11 | slug = AutoSlugField(populate_from="first_name", unique=True) 12 | location = models.CharField(max_length=100, blank=True) 13 | 14 | def save(self, *args, **kwargs): 15 | full_name = self.first_name + "-" + self.last_name 16 | self.slug = slugify(full_name) 17 | super(User, self).save(*args, **kwargs) 18 | 19 | 20 | class Follow(models.Model): 21 | from_user = models.ForeignKey( 22 | User, on_delete=models.CASCADE, related_name="from_people" 23 | ) 24 | to_user = models.ForeignKey( 25 | User, on_delete=models.CASCADE, related_name="to_people" 26 | ) 27 | 28 | def __str__(self): 29 | return f"{self.from_user} follows {self.to_user}" 30 | 31 | 32 | YEAR_CHOICES = [] 33 | for year in range(1980, (datetime.datetime.now().year + 1)): 34 | YEAR_CHOICES.append((year, year)) 35 | 36 | 37 | class WorkPlace(models.Model): 38 | company_name = models.CharField(max_length=100) 39 | position = models.CharField(max_length=100) 40 | start_year = models.IntegerField(choices=YEAR_CHOICES, null=True, blank=True,) 41 | end_year = models.IntegerField(choices=YEAR_CHOICES, null=True, blank=True,) 42 | is_currently_working = models.BooleanField(default=False) 43 | user = models.ForeignKey(User, on_delete=models.CASCADE) 44 | 45 | def __str__(self): 46 | return self.company_name 47 | 48 | 49 | class Education(models.Model): 50 | school_name = models.CharField(max_length=100) 51 | start_year = models.IntegerField(choices=YEAR_CHOICES, null=True, blank=True,) 52 | end_year = models.IntegerField(choices=YEAR_CHOICES, null=True, blank=True,) 53 | is_currently_studying = models.BooleanField(default=False) 54 | user = models.ForeignKey(User, on_delete=models.CASCADE) 55 | 56 | def __str__(self): 57 | return self.school_name 58 | -------------------------------------------------------------------------------- /users/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhkumarpratik/prashnottar-django/4db02d439ba6ec59d1473fef350a633930c9d9c1/users/templatetags/__init__.py -------------------------------------------------------------------------------- /users/templatetags/is_following.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from users.models import Follow 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.simple_tag 8 | def is_following(from_user, to_user): 9 | is_following = Follow.objects.filter(from_user=from_user, to_user=to_user) 10 | is_following = True if is_following else False 11 | return is_following 12 | -------------------------------------------------------------------------------- /users/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | app_name = "users" 5 | urlpatterns = [ 6 | path("login/", views.UserLoginView.as_view(), name="login"), 7 | path("profile/", views.ProfileDetailView.as_view(), name="profile"), 8 | path( 9 | "profile//questions", 10 | views.ProfileQuestionListView.as_view(), 11 | name="profile_questions", 12 | ), 13 | path( 14 | "profile//answers", 15 | views.ProfileAnswerListView.as_view(), 16 | name="profile_answers", 17 | ), 18 | path( 19 | "profile//answers//", 20 | views.profile_answer_pin, 21 | name="is_pin_answer", 22 | ), 23 | path( 24 | "profile//followers", 25 | views.ProfileFollowersListView.as_view(), 26 | name="followers", 27 | ), 28 | path( 29 | "profile//following", 30 | views.ProfileFollowingListView.as_view(), 31 | name="following", 32 | ), 33 | path("edit/", views.ProfileUpdateView.as_view(), name="edit"), 34 | path("add/workplace/", views.WorkPlaceFormAddView.as_view(), name="workplace_add"), 35 | path("edit/workplace/", views.WorkPlaceUpdateView.as_view(), name="workplace_edit"), 36 | path("delete/workplace/", views.delete_workplace, name="workplace_delete"), 37 | path("add/education/", views.EducationFormAddView.as_view(), name="education_add"), 38 | path("edit/education/", views.EducationUpdateView.as_view(), name="education_edit"), 39 | path("delete/education/", views.delete_education, name="education_delete",), 40 | path( 41 | "follow_toggle/", 42 | views.follow_unfollow_users, 43 | name="follow_unfollow", 44 | ), 45 | path("logout/", views.logout_request, name="logout"), 46 | path("register/", views.UserRegisterView.as_view(), name="register"), 47 | ] 48 | --------------------------------------------------------------------------------