├── 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 |

{{ question.title }} ({{ question.rating }})

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 |

{{ question.title }}

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 |
11 |
12 | {% csrf_token %} 13 | {% for field in form %} 14 |
15 |
16 | {{ field.label }} 17 |
18 |
19 | {{ field }} 20 |
21 | {{ field.errors }} 22 |
23 | {% endfor %} 24 | 25 |
26 |
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 |
11 |
12 | {% csrf_token %} 13 | {% for field in form %} 14 |
15 |
16 | {{ field.label }} 17 |
18 |
19 | {{ field }} 20 |
21 | {{ field.errors }} 22 |
23 | {% endfor %} 24 | 25 |
26 |
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 |
11 |
12 | {% csrf_token %} 13 | {% for field in form %} 14 |
15 |
16 | {{ field.label }} 17 |
18 |
19 | {{ field }} 20 |
21 | {{ field.errors }} 22 |
23 | {% endfor %} 24 | 25 |
26 |
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 |

Ask Pupkin

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 |
32 |
33 | {% csrf_token %} 34 | {% for field in form %} 35 |
36 | {{ field }} 37 | {{ field.errors }} 38 |
39 | {% endfor %} 40 | 41 |
42 |
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 | --------------------------------------------------------------------------------