├── .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 | 
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 |
18 |
19 |
20 |
21 |
22 |
24 |
25 |
26 |
27 |
36 | {% block buttons %}
37 | {% if request.user.is_authenticated %}
38 |
44 |
Ask Question
45 |
Logout
46 | {% else %}
47 |
Login
48 |
Register
49 | {% endif %}
50 | {% endblock buttons %}
51 |
52 |
53 |
54 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
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 |
13 |
15 |
18 |
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 |
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 |
48 |
49 | {% endfor %}
50 |
51 | {% else %}
52 | No Notifications
53 | {% endif %}
54 |
55 |
56 | {% if is_paginated %}
57 |
58 |
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 |
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 |
30 |
31 |
32 |
{{user_answer.question}}
33 |
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 |
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 |
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 |
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 |
14 | Uh, oh! {{ message }}
15 |
16 | ×
17 |
18 |
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 |
52 | {% else %}
53 |
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 |
8 | Uh, oh! {{ message }}
9 |
10 | ×
11 |
12 |
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 |
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 |
30 |
31 |
61 |
62 | {% if question.answer_set.count == 0 %}
63 |
No Answers
64 | {% else %}
65 | {% for answer in question.answer_set.all %}
66 |
67 |
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 |
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 | Sign in
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 |
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 | Register
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 |
5 | {% for answer in answers %}
6 | {% if not answer.is_anonymous %}
7 |
105 | {% elif answer.user == request.user %}
106 |
212 | {% endif %}
213 | {% endfor %}
214 |
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 |
61 | {% for answer in answers %}
62 |
63 | {% if not answer.is_anonymous %}
64 |
144 | {% endif %}
145 | {% endfor %}
146 |
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 |
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 |
181 |
182 | Add
183 | Workplace
184 |
185 | {% endif %}
186 |
187 |
188 | {% if user.education_set.all %}
189 |
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 |
208 |
209 | Add
210 | Education
211 |
212 | {% endif %}
213 |
214 |
215 | {% if user.location %}
216 |
217 |
Lives in {{user.location}}
218 | {% if user == request.user %}
219 |
220 | Edit
221 | Remove
222 |
223 | {% endif %}
224 | {% elif user == request.user %}
225 |
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 |
6 |
{{followers|length}} Followers
7 | {% if followers %}
8 | {% for follower in followers %}
9 |
10 |
49 |
50 | {% endfor %}
51 | {% else %}
52 |
{{user.first_name}} {{user.last_name}} has no followers yet.
53 | {% endif %}
54 |
55 | {% endblock profile %}
--------------------------------------------------------------------------------
/templates/users/user_following.html:
--------------------------------------------------------------------------------
1 | {% extends "users/user_detail.html" %}
2 | {% load static %}
3 | {% block profile %}
4 |
5 |
{{followings|length}} Following
6 | {% if followings %}
7 | {% for following in followings %}
8 |
9 |
41 |
42 | {% endfor %}
43 | {% else %}
44 |
{{user.first_name}} {{user.last_name}} is not following anyone yet.
45 | {% endif %}
46 |
47 | {% endblock profile %}
--------------------------------------------------------------------------------
/templates/users/user_questions.html:
--------------------------------------------------------------------------------
1 | {% extends "users/user_detail.html" %}
2 | {% load static %}
3 | {% block profile %}
4 |
5 | {% for question in questions %}
6 | {% if not question.is_anonymous %}
7 |
8 |
41 |
58 | {% if question.answer_set.all.0.ans %}
59 |
95 | {% else %}
96 |
106 | {% endif %}
107 |
108 | {% elif question.user == request.user %}
109 |
110 |
144 |
161 | {% if question.answer_set.all.0.ans %}
162 |
199 | {% else %}
200 |
210 | {% endif %}
211 |
212 | {% endif %}
213 | {% endfor %}
214 |
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 |
--------------------------------------------------------------------------------
Post your comment
85 |95 | {% blocktrans count comment_count=comment_count %} 96 | There is {{ comment_count }} comment below. 97 | {% plural %} 98 | There are {{ comment_count }} comments below. 99 | {% endblocktrans %} 100 |
101 | {% endif %} 102 | 103 | {% if comment_count %} 104 |105 |
106 | {% render_xtdcomment_tree for user_answer %} 107 |
108 | {% endif %} 109 |