├── ask
├── qa
│ ├── __init__.py
│ ├── migrations
│ │ ├── __init__.py
│ │ └── 0001_initial.py
│ ├── templates
│ │ ├── error.html
│ │ ├── list_rating.html
│ │ ├── pagination.html
│ │ ├── list.html
│ │ ├── login.html
│ │ ├── signup.html
│ │ ├── ask.html
│ │ ├── base.html
│ │ └── detail.html
│ ├── admin.py
│ ├── urls.py
│ ├── models.py
│ ├── static
│ │ └── css
│ │ │ └── qa.css
│ ├── views.py
│ └── forms.py
├── ask
│ ├── __init__.py
│ ├── wsgi.py
│ ├── urls.py
│ └── settings.py
└── manage.py
├── public
├── js
│ └── .gitkeep
├── css
│ └── .gitkeep
└── img
│ └── .gitkeep
├── uploads
└── .gitkeep
├── requirements.txt
├── .gitignore
├── README.md
├── etc
├── gunicorn_ask.conf
└── nginx.conf
└── init.sh
/ask/qa/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/js/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/uploads/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ask/ask/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/css/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ask/qa/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | django
2 | mysqlclient
3 | gunicorn
4 | flake8
5 | autopep8
6 | django_autofixture
7 | pytz
8 | django-debug-toolbar
9 |
--------------------------------------------------------------------------------
/ask/qa/templates/error.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}{{ error }} // Ask Pupkin{% endblock %}
4 |
5 | {% block content %}
6 |
{{ error }}
7 | {% endblock %}
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Local
2 | *_local*
3 | static
4 | env
5 |
6 | # Temporary files
7 | *.pyc
8 | __pycache__
9 | *.swp
10 | *.log
11 | log
12 |
13 | # Test data
14 | *fixtures*
15 |
16 | # Database
17 | *.sqlite3
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Stepic web project
2 |
3 | Clone project:
4 |
5 | git clone https://github.com/kovtunos/stepic_web_project.git web
6 |
7 | Local init:
8 |
9 | virtualenv env -p `which python3`
10 | source env/bin/activate
11 | pip install -r requirements.txt
12 |
--------------------------------------------------------------------------------
/ask/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ask.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/etc/gunicorn_ask.conf:
--------------------------------------------------------------------------------
1 | CONFIG = {
2 | 'mode': 'wsgi',
3 | 'working_dir': '/home/box/web/ask/ask',
4 | 'python': '/usr/bin/python',
5 | 'args': (
6 | '--bind=0.0.0.0:8000',
7 | '--workers=5',
8 | '--timeout=60',
9 | '--log-file=/home/box/gunicorn.log',
10 | 'wsgi',
11 | ),
12 | }
13 |
--------------------------------------------------------------------------------
/ask/qa/templates/list_rating.html:
--------------------------------------------------------------------------------
1 | {% extends "list.html" %}
2 |
3 | {% block title %}Popular // Ask Pupkin{% endblock %}
4 |
5 | {% block question_view_title %}Popular Questions
{% endblock %}
6 |
7 | {% block question_title %}
8 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/ask/qa/templates/pagination.html:
--------------------------------------------------------------------------------
1 | {% if paginator.num_pages > 1 %}
2 |
15 | {% endif %}
16 |
--------------------------------------------------------------------------------
/ask/qa/templates/list.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %} Ask Pupkin {% endblock %}
4 |
5 | {% block content %}
6 | {% block question_view_title %}New Questions
{% endblock %}
7 | {% for question in questions %}
8 | {% block question_title %}
9 |
10 | {% endblock %}
11 | {% endfor %}
12 |
13 | {% include "pagination.html" %}
14 | {% endblock %}
15 |
--------------------------------------------------------------------------------
/etc/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80 default_server;
3 | server_name localhost;
4 |
5 | root /home/box/web;
6 |
7 | # ask app
8 | location ^~ / {
9 | proxy_pass http://0.0.0.0:8000;
10 | }
11 |
12 | # location ~* ^.+\.(jpg|jpeg|gif|png|js|css)$ {
13 | # root /home/box/web/public;
14 | # }
15 |
16 | location = /favicon.ico {
17 | access_log off;
18 | log_not_found off;
19 | }
20 |
21 | error_log /home/box/nginx.log;
22 | }
23 |
--------------------------------------------------------------------------------
/ask/ask/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for mysite 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/1.9/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 | import sys
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | sys.path.append('/home/box/web/ask')
15 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ask.settings")
16 |
17 | application = get_wsgi_application()
18 |
--------------------------------------------------------------------------------
/ask/qa/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Question, Answer
3 | import pytz
4 |
5 |
6 | class QuestionAdmin(admin.ModelAdmin):
7 | list_display = ('title', 'text', 'added_at', 'author', 'rating')
8 | date_hierarchy = 'added_at'
9 |
10 |
11 | class AnswerAdmin(admin.ModelAdmin):
12 | list_display = ('text', 'added_at', 'author')
13 | date_hierarchy = 'added_at'
14 | raw_id_fields = ('question',)
15 |
16 |
17 | admin.site.register(Question, QuestionAdmin)
18 | admin.site.register(Answer, AnswerAdmin)
19 |
--------------------------------------------------------------------------------
/ask/qa/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import url
2 | from qa.views import test, question_list, question_detail, popular
3 | from qa.views import question_ask, question_answer
4 | from qa.views import user_signup, user_login, user_logout
5 |
6 | urlpatterns = [
7 | url(r'^$', question_list, name='question_list'),
8 | url(r'^question/(?P\d+)/', question_detail, name='question_detail'),
9 | url(r'^popular/', popular, name='popular'),
10 | url(r'^ask/', question_ask, name='question_ask'),
11 | url(r'^answer/', question_answer, name='question_answer'),
12 | url(r'^signup/', user_signup, name='signup'),
13 | url(r'^login/', user_login, name='login'),
14 | url(r'^logout/', user_logout, name='logout'),
15 | url(r'^new/', test, name='new'),
16 | ]
17 |
--------------------------------------------------------------------------------
/ask/qa/templates/login.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}Log In // Ask Pupkin{% endblock %}
4 |
5 | {% block content %}
6 | Sign Up
7 | {% for e in form.non_field_errors %}
8 | {{ e }}
9 | {% endfor %}
10 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/ask/qa/templates/signup.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}Sign Up // Ask Pupkin{% endblock %}
4 |
5 | {% block content %}
6 | Sign Up
7 | {% for e in form.non_field_errors %}
8 | {{ e }}
9 | {% endfor %}
10 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/ask/qa/templates/ask.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}Ask Question // Ask Pupkin{% endblock %}
4 |
5 | {% block content %}
6 | Ask Question
7 | {% for e in form.non_field_errors %}
8 | {{ e }}
9 | {% endfor %}
10 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/init.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Install
4 | sudo pip install django-autofixture pytz
5 | sudo apt-get install -y w3m
6 |
7 | # Nginx
8 | if [ -f /etc/nginx/sites-enabled/default ]; then
9 | sudo rm /etc/nginx/sites-enabled/default
10 | fi
11 | touch /home/box/nginx.log
12 | sudo ln -sf /home/box/web/etc/nginx.conf /etc/nginx/sites-enabled/ask.conf
13 | sudo /etc/init.d/nginx restart
14 |
15 | # Gunicorn (ver. 17.5)
16 | touch /home/box/gunicorn.log
17 | sudo ln -sf /home/box/web/etc/gunicorn_ask.conf /etc/gunicorn.d/ask
18 | sudo /etc/init.d/gunicorn restart
19 |
20 | # MySQL
21 | echo 'innodb_use_native_aio = 0' | sudo tee --append /etc/mysql/my.cnf
22 | sudo service mysql restart
23 | sudo mysql -uroot -e "CREATE DATABASE ask CHARACTER SET utf8 COLLATE utf8_general_ci;"
24 | sudo mysql -uroot -e "GRANT ALL PRIVILEGES ON ask.* TO 'ask_user'@'localhost' IDENTIFIED BY '123456789';"
25 |
--------------------------------------------------------------------------------
/ask/ask/urls.py:
--------------------------------------------------------------------------------
1 | """ask URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/1.9/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: url(r'^$', 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: url(r'^$', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.conf.urls import url, include
14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
15 | """
16 | from django.conf.urls import include, url
17 |
18 | from django.contrib import admin
19 | admin.autodiscover()
20 |
21 | urlpatterns = [
22 | url(r'^', include('qa.urls')),
23 | url(r'^admin/', admin.site.urls),
24 | ]
25 |
--------------------------------------------------------------------------------
/ask/qa/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.contrib.auth.models import User
3 | from django.core.urlresolvers import reverse
4 |
5 |
6 | class Question(models.Model):
7 | title = models.CharField(max_length=255)
8 | text = models.TextField()
9 | added_at = models.DateTimeField(auto_now_add=True)
10 | rating = models.IntegerField(default=0)
11 | author = models.ForeignKey(User, related_name="question_author")
12 | likes = models.ManyToManyField(
13 | User, related_name="question_like", blank=True)
14 |
15 | class Meta:
16 | ordering = ('-added_at',)
17 |
18 | def __str__(self):
19 | return self.title
20 |
21 | def get_absolute_url(self):
22 | return reverse('question_detail', kwargs={'pk': self.pk})
23 |
24 |
25 | class Answer(models.Model):
26 | text = models.TextField()
27 | added_at = models.DateTimeField(auto_now_add=True)
28 | question = models.ForeignKey(Question)
29 | author = models.ForeignKey(User)
30 |
31 | class Meta:
32 | ordering = ('added_at',)
33 |
34 | def __str__(self):
35 | return 'Answer by {}'.format(self.author)
36 |
--------------------------------------------------------------------------------
/ask/qa/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
3 |
4 |
5 | {% block title %}Ask Pupkin{% endblock %}
6 |
7 |
8 |
9 |
10 |
11 | Have a question? Ask me.
12 |
13 |
14 |
15 | {% block content %}{% endblock %}
16 |
17 |
28 |
29 |
30 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ask/qa/templates/detail.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}{{ question.title }}{% endblock %}
4 |
5 | {% block content %}
6 | {{ question.title }}
7 |
8 | Published {{ question.added_at }} by {{ question.author }}
9 |
10 | {{ question.text|linebreaks }}
11 |
12 | {% block answers %}
13 | Answers:
14 | {% for answer in answers %}
15 |
16 |
17 | Answer by {{ answer.author }} [{{ answer.added_at }}]
18 |
19 | {{ answer.text|linebreaks }}
20 |
21 | {% empty %}
22 | There are no answers yet.
23 | {% endfor %}
24 | {% endblock %}
25 |
26 | {% block question_ask %}
27 | Your answer:
28 | {% for e in form.non_field_errors %}
29 | {{ e }}
30 | {% endfor %}
31 |
43 | {%endblock%}
44 |
45 | {% endblock %}
46 |
--------------------------------------------------------------------------------
/ask/qa/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.9.4 on 2016-03-19 14:26
3 | from __future__ import unicode_literals
4 |
5 | from django.conf import settings
6 | from django.db import migrations, models
7 | import django.db.models.deletion
8 |
9 |
10 | class Migration(migrations.Migration):
11 |
12 | initial = True
13 |
14 | dependencies = [
15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
16 | ]
17 |
18 | operations = [
19 | migrations.CreateModel(
20 | name='Answer',
21 | fields=[
22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23 | ('text', models.TextField()),
24 | ('added_at', models.DateTimeField(auto_now_add=True)),
25 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
26 | ],
27 | options={
28 | 'ordering': ('added_at',),
29 | },
30 | ),
31 | migrations.CreateModel(
32 | name='Question',
33 | fields=[
34 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
35 | ('title', models.CharField(max_length=255)),
36 | ('text', models.TextField()),
37 | ('added_at', models.DateTimeField(auto_now_add=True)),
38 | ('rating', models.IntegerField(default=0)),
39 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='question_author', to=settings.AUTH_USER_MODEL)),
40 | ('likes', models.ManyToManyField(blank=True, related_name='question_like', to=settings.AUTH_USER_MODEL)),
41 | ],
42 | options={
43 | 'ordering': ('-added_at',),
44 | },
45 | ),
46 | migrations.AddField(
47 | model_name='answer',
48 | name='question',
49 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='qa.Question'),
50 | ),
51 | ]
52 |
--------------------------------------------------------------------------------
/ask/qa/static/css/qa.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: helvetica, sans-serif;
5 | position: relative;
6 | }
7 | a {
8 | color: #00abff;
9 | text-decoration: none;
10 | }
11 | a:hover {
12 | color: #0B8BD2;
13 | }
14 | h1 {
15 | font-weight: normal;
16 | font-size: 2.2em;
17 | border-bottom: 1px solid #bbb;
18 | padding: 0 0 10px 0;
19 | }
20 | h2 {
21 | font-size: 1.2em;
22 | font-weight: normal;
23 | margin: 30px 0 0;
24 | }
25 | header {
26 | background-color: #00abff;
27 | color: #FFF;
28 | height: 100px;
29 | overflow: hidden;
30 | }
31 | .logo {
32 | margin: 15px 0 0 30px;
33 | border: none;
34 | margin-left: 30px;
35 | }
36 | .slogan {
37 | margin: 0 0 0 30px;
38 | font-size: 1em;
39 | }
40 | .copy {
41 | padding-right: 30px;
42 | float: right;
43 | font-size:.8em;
44 | }
45 | .adm {
46 | float: left;
47 | font-size:.8em;
48 | padding-left: 30px;
49 | }
50 | footer {
51 | background-color: #00abff;
52 | color: #FFF;
53 | height: 36px;
54 | position: absolute;
55 | width: 100%;
56 | }
57 | footer a,
58 | header a {
59 | color: #FFF;
60 | }
61 | footer a:hover,
62 | header a:hover {
63 | color: #FFF;
64 | }
65 | .wrapper {
66 | clear: both;
67 | }
68 | .content {
69 | float: left;
70 | padding: 0 0 0 30px;
71 | width: 60%;
72 | margin-bottom: 40px;
73 | }
74 | .sidebar {
75 | background: #EAF2FD;
76 | float: right;
77 | height: 100%;
78 | padding: 10px;
79 | width: 30%;
80 | }
81 | .pagination {
82 | margin: 40px 0;
83 | font-weight: bold;
84 | text-align: center;
85 | }
86 | .pagination .active a {
87 | color: white;
88 | text-align: center;
89 | }
90 | ul.pagination li {
91 | display: inline;
92 | list-style: none;
93 | margin: 0 15px;
94 | }
95 | .pagination .active {
96 | border-radius: 50%;
97 | background: #00abff;
98 | padding: 10px 10px 10px 14px;
99 | text-align: center;
100 | }
101 | .submitted {
102 | color: #ccc;
103 | font-family: georgia, serif;
104 | font-size: 12px;
105 | font-style: italic;
106 | }
107 | .clear {
108 | clear: both;
109 | }
110 | .answer {
111 | background-color: #EAF2FD;
112 | margin: 10px 0 10px 30px;
113 | padding: 10px 20px;
114 | }
115 | .info {
116 | color: #0B8BD2;
117 | }
118 |
119 | /* Forms */
120 | .form label {
121 | display: block;
122 | }
123 | .form input {
124 | padding: 5px;
125 | }
126 | .form-field {
127 | margin-bottom: 10px;
128 | }
129 | .alert {
130 | color: red;
131 | }
132 | .alert input,
133 | .alert textarea {
134 | background-color: #FFE8E8;
135 | border: 1px solid red;
136 | }
137 |
--------------------------------------------------------------------------------
/ask/ask/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for ask project.
3 |
4 | Generated by 'django-admin startproject' using Django 1.9.4.
5 | For more information on this file, see
6 | https://docs.djangoproject.com/en/1.9/topics/settings/
7 |
8 | For the full list of settings and their values, see
9 | https://docs.djangoproject.com/en/1.9/ref/settings/
10 | """
11 |
12 | import os
13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
14 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
15 |
16 |
17 | # Quick-start development settings - unsuitable for production
18 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
19 |
20 | # SECURITY WARNING: keep the secret key used in production secret!
21 | SECRET_KEY = 'p(vz0&33$-848_1cnsic386x0)@3ddqzu!1juumzoi1gh%xti('
22 |
23 | # SECURITY WARNING: don't run with debug turned on in production!
24 | DEBUG = True
25 |
26 | ALLOWED_HOSTS = []
27 |
28 |
29 | # Application definition
30 |
31 | INSTALLED_APPS = [
32 | 'django.contrib.admin',
33 | 'django.contrib.auth',
34 | 'django.contrib.contenttypes',
35 | 'django.contrib.sessions',
36 | 'django.contrib.messages',
37 | 'django.contrib.staticfiles',
38 | 'autofixture',
39 | 'debug_toolbar',
40 | 'qa',
41 | ]
42 |
43 | MIDDLEWARE_CLASSES = [
44 | 'django.contrib.sessions.middleware.SessionMiddleware',
45 | 'django.middleware.common.CommonMiddleware',
46 | 'django.middleware.csrf.CsrfViewMiddleware',
47 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
48 | 'django.contrib.messages.middleware.MessageMiddleware',
49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
50 | ]
51 |
52 | ROOT_URLCONF = 'ask.urls'
53 | TEMPLATES = [
54 | {
55 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
56 | 'DIRS': [],
57 | 'APP_DIRS': True,
58 | 'OPTIONS': {
59 | 'context_processors': [
60 | 'django.template.context_processors.debug',
61 | 'django.template.context_processors.request',
62 | 'django.contrib.auth.context_processors.auth',
63 | 'django.contrib.messages.context_processors.messages',
64 | ],
65 | },
66 | },
67 | ]
68 |
69 | WSGI_APPLICATION = 'ask.wsgi.application'
70 |
71 |
72 | # Database
73 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases
74 |
75 | # DATABASES = {
76 | # 'default': {
77 | # 'ENGINE': 'django.db.backends.sqlite3',
78 | # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
79 | # }
80 | # }
81 |
82 | DATABASES = {
83 | 'default': {
84 | 'ENGINE': 'django.db.backends.mysql',
85 | 'NAME': 'ask',
86 | 'USER': 'ask_user',
87 | 'PASSWORD': '123456789',
88 | 'HOST': '',
89 | 'PORT': '3306',
90 | }
91 | }
92 |
93 | # Password validation
94 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
95 |
96 | AUTH_PASSWORD_VALIDATORS = []
97 |
98 | # Internationalization
99 | # https://docs.djangoproject.com/en/1.9/topics/i18n/
100 |
101 | LANGUAGE_CODE = 'en-us'
102 |
103 | TIME_ZONE = 'Europe/Moscow'
104 |
105 | USE_I18N = True
106 |
107 | USE_L10N = True
108 |
109 | USE_TZ = True
110 |
111 |
112 | # Static files (CSS, JavaScript, Images)
113 | # https://docs.djangoproject.com/en/1.9/howto/static-files/
114 |
115 | STATIC_URL = '/static/'
116 |
117 |
118 | # Load local settings
119 | try:
120 | from ask.settings_local import *
121 | except ImportError:
122 | pass
123 |
--------------------------------------------------------------------------------
/ask/qa/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render, get_object_or_404, redirect
2 | from django.http import Http404, HttpResponse, HttpResponseRedirect
3 | from django.core.paginator import Paginator, EmptyPage
4 | from django.core.urlresolvers import reverse
5 | from django.contrib.auth import login, logout
6 | from qa.models import Question
7 | from qa.forms import AskForm, AnswerForm, LoginForm, SignupForm
8 |
9 |
10 | def test(request, *args, **kwargs):
11 | return HttpResponse('OK')
12 |
13 |
14 | def paginate(request, qs):
15 | try:
16 | limit = int(request.GET.get('limit', 10))
17 | except ValueError:
18 | limit = 10
19 | if limit > 100:
20 | limit = 10
21 |
22 | try:
23 | page = int(request.GET.get('page', 1))
24 | except ValueError:
25 | raise Http404
26 |
27 | paginator = Paginator(qs, limit)
28 |
29 | try:
30 | page = paginator.page(page)
31 | except EmptyPage:
32 | page = paginator.page(paginator.num_pages)
33 | return page, paginator
34 |
35 |
36 | def question_list(request):
37 | qs = Question.objects.all()
38 | qs = qs.order_by('-added_at')
39 | page, paginator = paginate(request, qs)
40 | paginator.baseurl = reverse('question_list') + '?page='
41 |
42 | return render(request, 'list.html', {
43 | 'questions': page.object_list,
44 | 'page': page,
45 | 'paginator': paginator,
46 | })
47 |
48 |
49 | def popular(request):
50 | qs = Question.objects.all()
51 | qs = qs.order_by('-rating')
52 | page, paginator = paginate(request, qs)
53 | paginator.baseurl = reverse('popular') + '?page='
54 |
55 | return render(request, 'list_rating.html', {
56 | 'questions': page.object_list,
57 | 'page': page,
58 | 'paginator': paginator,
59 | })
60 |
61 |
62 | def question_detail(request, pk):
63 | question = get_object_or_404(Question, id=pk)
64 | answers = question.answer_set.all()
65 | form = AnswerForm(initial={'question': str(pk)})
66 | return render(request, 'detail.html', {
67 | 'question': question,
68 | 'answers': answers,
69 | 'form': form,
70 | })
71 |
72 |
73 | def question_ask(request):
74 | if request.method == 'POST':
75 | form = AskForm(request.POST)
76 | if form.is_valid():
77 | form._user = request.user
78 | ask = form.save()
79 | url = reverse('question_detail', args=[ask.id])
80 | return HttpResponseRedirect(url)
81 | else:
82 | form = AskForm()
83 |
84 | return render(request, 'ask.html', {
85 | 'form': form
86 | })
87 |
88 |
89 | def question_answer(request):
90 | if request.method == 'POST':
91 | form = AnswerForm(request.POST)
92 | if form.is_valid():
93 | form._user = request.user
94 | answer = form.save()
95 | url = reverse('question_detail', args=[answer.question.id])
96 | return HttpResponseRedirect(url)
97 | return HttpResponseRedirect('/')
98 |
99 |
100 | def user_signup(request):
101 | if request.method == 'POST':
102 | form = SignupForm(request.POST)
103 | if form.is_valid():
104 | user = form.save()
105 | if user is not None:
106 | login(request, user)
107 | return HttpResponseRedirect('/')
108 | form = SignupForm()
109 | return render(request, 'signup.html', {'form': form})
110 |
111 |
112 | def user_login(request):
113 | if request.method == 'POST':
114 | form = LoginForm(request.POST)
115 | if form.is_valid():
116 | user = form.save()
117 | if user is not None:
118 | login(request, user)
119 | return HttpResponseRedirect('/')
120 | form = LoginForm()
121 | return render(request, 'login.html', {'form': form})
122 |
123 |
124 | def user_logout(request):
125 | logout(request)
126 | return redirect('login')
127 |
--------------------------------------------------------------------------------
/ask/qa/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.shortcuts import get_object_or_404
3 | from django.contrib.auth.models import User
4 | from django.contrib.auth import authenticate
5 | from qa.models import Question, Answer
6 |
7 |
8 | class AskForm(forms.Form):
9 | title = forms.CharField(max_length=255)
10 | text = forms.CharField(widget=forms.Textarea)
11 |
12 | def clean_title(self):
13 | title = self.cleaned_data['title']
14 | if title.strip() == '':
15 | raise forms.ValidationError(
16 | u'Title is empty', code='validation_error')
17 | return title
18 |
19 | def clean_text(self):
20 | text = self.cleaned_data['text']
21 | if text.strip() == '':
22 | raise forms.ValidationError(
23 | u'Text is empty', code='validation_error')
24 | return text
25 |
26 | def save(self):
27 | if self._user.is_anonymous():
28 | self.cleaned_data['author_id'] = 1
29 | else:
30 | self.cleaned_data['author'] = self._user
31 | ask = Question(**self.cleaned_data)
32 | ask.save()
33 | return ask
34 |
35 |
36 | class AnswerForm(forms.Form):
37 | text = forms.CharField(widget=forms.Textarea)
38 | question = forms.IntegerField(widget=forms.HiddenInput)
39 |
40 | def clean_text(self):
41 | text = self.cleaned_data['text']
42 | if text.strip() == '':
43 | raise forms.ValidationError(
44 | u'Text is empty', code='validation_error')
45 | return text
46 |
47 | def clean_question(self):
48 | question = self.cleaned_data['question']
49 | if question == 0:
50 | raise forms.ValidationError(u'Question number incorrect',
51 | code='validation_error')
52 | return question
53 |
54 | def save(self):
55 | self.cleaned_data['question'] = get_object_or_404(
56 | Question, pk=self.cleaned_data['question'])
57 | if self._user.is_anonymous():
58 | self.cleaned_data['author_id'] = 1
59 | else:
60 | self.cleaned_data['author'] = self._user
61 | answer = Answer(**self.cleaned_data)
62 | answer.save()
63 | return answer
64 |
65 |
66 | class SignupForm(forms.Form):
67 | username = forms.CharField(max_length=100)
68 | email = forms.EmailField()
69 | password = forms.CharField(widget=forms.PasswordInput)
70 |
71 | def clean_username(self):
72 | username = self.cleaned_data['username']
73 | if username.strip() == '':
74 | raise forms.ValidationError(
75 | u'Username is empty', code='validation_error')
76 | return username
77 |
78 | def clean_email(self):
79 | email = self.cleaned_data['email']
80 | if email.strip() == '':
81 | raise forms.ValidationError(
82 | u'Email is empty', code='validation_error')
83 | return email
84 |
85 | def clean_password(self):
86 | password = self.cleaned_data['password']
87 | if password.strip() == '':
88 | raise forms.ValidationError(
89 | u'Password is empty', code='validation_error')
90 | return password
91 |
92 | def save(self):
93 | user = User.objects.create_user(**self.cleaned_data)
94 | user.save()
95 | auth = authenticate(**self.cleaned_data)
96 | return auth
97 |
98 |
99 | class LoginForm(forms.Form):
100 | username = forms.CharField(max_length=100)
101 | password = forms.CharField(widget=forms.PasswordInput)
102 |
103 | def clean_username(self):
104 | username = self.cleaned_data['username']
105 | if username.strip() == '':
106 | raise forms.ValidationError(
107 | u'Username is empty', code='validation_error')
108 | return username
109 |
110 | def clean_password(self):
111 | password = self.cleaned_data['password']
112 | if password.strip() == '':
113 | raise forms.ValidationError(
114 | u'Password is empty', code='validation_error')
115 | return password
116 |
117 | def save(self):
118 | user = authenticate(**self.cleaned_data)
119 | return user
120 |
--------------------------------------------------------------------------------