From 9868c68d012cd9ce5a4653df0eef6a72face7ac5 Mon Sep 17 00:00:00 2001 From: Ivopires Date: Mon, 17 Sep 2018 17:43:33 +0100 Subject: [PATCH 01/15] Structure of the challenge completed! All endpoints working --- challenge/challenge/__init__.py | 0 challenge/challenge/settings.py | 118 ++++++++++++++++++++ challenge/challenge/urls.py | 22 ++++ challenge/challenge/wsgi.py | 16 +++ challenge/list_posts/__init__.py | 0 challenge/list_posts/admin.py | 6 + challenge/list_posts/apps.py | 8 ++ challenge/list_posts/migrations/__init__.py | 0 challenge/list_posts/models.py | 6 + challenge/list_posts/tests.py | 6 + challenge/list_posts/urls.py | 9 ++ challenge/list_posts/views.py | 23 ++++ challenge/manage.py | 22 ++++ 13 files changed, 236 insertions(+) create mode 100644 challenge/challenge/__init__.py create mode 100644 challenge/challenge/settings.py create mode 100644 challenge/challenge/urls.py create mode 100644 challenge/challenge/wsgi.py create mode 100644 challenge/list_posts/__init__.py create mode 100644 challenge/list_posts/admin.py create mode 100644 challenge/list_posts/apps.py create mode 100644 challenge/list_posts/migrations/__init__.py create mode 100644 challenge/list_posts/models.py create mode 100644 challenge/list_posts/tests.py create mode 100644 challenge/list_posts/urls.py create mode 100644 challenge/list_posts/views.py create mode 100755 challenge/manage.py diff --git a/challenge/challenge/__init__.py b/challenge/challenge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/challenge/challenge/settings.py b/challenge/challenge/settings.py new file mode 100644 index 0000000..145f947 --- /dev/null +++ b/challenge/challenge/settings.py @@ -0,0 +1,118 @@ +""" +Django settings for challenge project. + +Generated by 'django-admin startproject' using Django 1.11.15. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.11/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'hj&%tr8llp&+n*nrq_c!8p7$#f=ocd3w86iq=eh88@kv0-^1=r' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'challenge.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'challenge.wsgi.application' + +# Database +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + +# Password validation +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': + 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': + 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': + 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': + 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/1.11/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.11/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/challenge/challenge/urls.py b/challenge/challenge/urls.py new file mode 100644 index 0000000..7e81be3 --- /dev/null +++ b/challenge/challenge/urls.py @@ -0,0 +1,22 @@ +"""challenge URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.11/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url, include +from django.contrib import admin + +urlpatterns = [ + url(r'^', include('list_posts.urls')), + url(r'^admin/', admin.site.urls), +] diff --git a/challenge/challenge/wsgi.py b/challenge/challenge/wsgi.py new file mode 100644 index 0000000..41e4e35 --- /dev/null +++ b/challenge/challenge/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for challenge project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "challenge.settings") + +application = get_wsgi_application() diff --git a/challenge/list_posts/__init__.py b/challenge/list_posts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/challenge/list_posts/admin.py b/challenge/list_posts/admin.py new file mode 100644 index 0000000..13be29d --- /dev/null +++ b/challenge/list_posts/admin.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.contrib import admin + +# Register your models here. diff --git a/challenge/list_posts/apps.py b/challenge/list_posts/apps.py new file mode 100644 index 0000000..e32a0fd --- /dev/null +++ b/challenge/list_posts/apps.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class ListPostsConfig(AppConfig): + name = 'list_posts' diff --git a/challenge/list_posts/migrations/__init__.py b/challenge/list_posts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/challenge/list_posts/models.py b/challenge/list_posts/models.py new file mode 100644 index 0000000..1dfab76 --- /dev/null +++ b/challenge/list_posts/models.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models + +# Create your models here. diff --git a/challenge/list_posts/tests.py b/challenge/list_posts/tests.py new file mode 100644 index 0000000..5982e6b --- /dev/null +++ b/challenge/list_posts/tests.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.test import TestCase + +# Create your tests here. diff --git a/challenge/list_posts/urls.py b/challenge/list_posts/urls.py new file mode 100644 index 0000000..2a1115f --- /dev/null +++ b/challenge/list_posts/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls import url +from . import views + +urlpatterns = [ + url(r'^$', views.index, name='index'), + url(r'^posts/', views.list_posts, name='list_posts'), + url(r'^upvote/(?P[0-9]+)/$', views.up_vote, name='list_posts'), + url(r'^downvote/(?P[0-9]+)/$', views.down_vote, name='list_posts'), +] diff --git a/challenge/list_posts/views.py b/challenge/list_posts/views.py new file mode 100644 index 0000000..8cf601d --- /dev/null +++ b/challenge/list_posts/views.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.shortcuts import render +from django.http import HttpResponse + + +def index(request): + return HttpResponse("Your posts will be posted here") + + +def list_posts(request): + return HttpResponse("Here are the posts listed by top rating") + + +def up_vote(request, post_id): + return HttpResponse( + "You just upvoted the post with the ID {}".format(post_id)) + + +def down_vote(request, post_id): + return HttpResponse( + "You just downvoted the post with the ID {}".format(post_id)) diff --git a/challenge/manage.py b/challenge/manage.py new file mode 100755 index 0000000..81d3205 --- /dev/null +++ b/challenge/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "challenge.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) From 92165d92578ad0a9807c63df6b7b580b178cb206 Mon Sep 17 00:00:00 2001 From: Ivopires Date: Tue, 18 Sep 2018 01:36:11 +0100 Subject: [PATCH 02/15] Added templates for the index.html and post.html! Post model created --- challenge/challenge/settings.py | 1 + .../list_posts/migrations/0001_initial.py | 25 ++++++++++++ challenge/list_posts/models.py | 39 ++++++++++++++++++- .../templates/list_posts/index.html | 11 ++++++ .../list_posts/templates/list_posts/post.html | 22 +++++++++++ challenge/list_posts/urls.py | 7 +++- challenge/list_posts/views.py | 39 ++++++++++++++++++- 7 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 challenge/list_posts/migrations/0001_initial.py create mode 100644 challenge/list_posts/templates/list_posts/index.html create mode 100644 challenge/list_posts/templates/list_posts/post.html diff --git a/challenge/challenge/settings.py b/challenge/challenge/settings.py index 145f947..76e564a 100644 --- a/challenge/challenge/settings.py +++ b/challenge/challenge/settings.py @@ -29,6 +29,7 @@ # Application definition INSTALLED_APPS = [ + 'list_posts.apps.ListPostsConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', diff --git a/challenge/list_posts/migrations/0001_initial.py b/challenge/list_posts/migrations/0001_initial.py new file mode 100644 index 0000000..e51624f --- /dev/null +++ b/challenge/list_posts/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.15 on 2018-09-17 22:48 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Post', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('post_text', models.CharField(max_length=500, verbose_name='Post content')), + ('up_votes', models.PositiveIntegerField(default=0, verbose_name='Up votes')), + ('down_votes', models.PositiveIntegerField(default=0, verbose_name='Down votes')), + ], + ), + ] diff --git a/challenge/list_posts/models.py b/challenge/list_posts/models.py index 1dfab76..8d3545b 100644 --- a/challenge/list_posts/models.py +++ b/challenge/list_posts/models.py @@ -1,6 +1,43 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import numpy as np from django.db import models +from django.utils.encoding import python_2_unicode_compatible -# Create your models here. + +@python_2_unicode_compatible +class Post(models.Model): + post_text = models.CharField('Post content', max_length=500) + up_votes = models.PositiveIntegerField('Up votes', default=0) + down_votes = models.PositiveIntegerField('Down votes', default=0) + + def __str__(self): + return self.post_text + + def compute_score(self): + """ + The compute_score function will compute the Wilson-score Interval, + depending on the number of up/down votes and the total number of votes. + Where: + - p_hat - is the fraction of up votes out of total votes + - total_votes - is the total number of votes + - z - is the normal distribution, which, in order to have 0.95 + of confidence, has to be equal to 1.96 + """ + + total_votes = self.up_votes + self.down_votes + if self.up_votes == 0: + return 0 + else: + p_hat = np.float64(self.up_votes / total_votes) + z = np.float64(1.96) + + lower_bound = [(p_hat + (z * z / (2 * total_votes)) - + (z * np.sqrt(p_hat * (1 - p_hat) + z * z / + (4 * total_votes))) / total_votes) / + (1 + (z * z / total_votes))] + + return lower_bound + + score = property(compute_score) diff --git a/challenge/list_posts/templates/list_posts/index.html b/challenge/list_posts/templates/list_posts/index.html new file mode 100644 index 0000000..d796751 --- /dev/null +++ b/challenge/list_posts/templates/list_posts/index.html @@ -0,0 +1,11 @@ +{% if latest_posts_list %} + +{% else %} +

No posts are available.

+{% endif %} diff --git a/challenge/list_posts/templates/list_posts/post.html b/challenge/list_posts/templates/list_posts/post.html new file mode 100644 index 0000000..04b8ed6 --- /dev/null +++ b/challenge/list_posts/templates/list_posts/post.html @@ -0,0 +1,22 @@ +

Post with id {{post.id}}

+ +

{{post.post_text}}

+ +

Results:{{post.up_votes}} vote{{post.up_votes|pluralize}} -- {{post.down_votes}} + vote{{post.down_votes|pluralize}}

+ +{% if error_message %}

{{error_message}}

{% endif %} + +
+ {% csrf_token %} + + +
+
+ + +
+
+ +
+Go home? diff --git a/challenge/list_posts/urls.py b/challenge/list_posts/urls.py index 2a1115f..9b37a45 100644 --- a/challenge/list_posts/urls.py +++ b/challenge/list_posts/urls.py @@ -1,9 +1,12 @@ from django.conf.urls import url from . import views +app_name = 'list_posts' urlpatterns = [ url(r'^$', views.index, name='index'), + url(r'^(?P[0-9]+)/$', views.PostView.as_view(), name='post'), + url(r'^vote/(?P[0-9]+)/$', views.vote, name='vote'), + url(r'^upvote/(?P[0-9]+)/$', views.up_vote, name='up_vote'), + url(r'^downvote/(?P[0-9]+)/$', views.down_vote, name='down_vote'), url(r'^posts/', views.list_posts, name='list_posts'), - url(r'^upvote/(?P[0-9]+)/$', views.up_vote, name='list_posts'), - url(r'^downvote/(?P[0-9]+)/$', views.down_vote, name='list_posts'), ] diff --git a/challenge/list_posts/views.py b/challenge/list_posts/views.py index 8cf601d..ccccd6b 100644 --- a/challenge/list_posts/views.py +++ b/challenge/list_posts/views.py @@ -2,11 +2,46 @@ from __future__ import unicode_literals from django.shortcuts import render -from django.http import HttpResponse +from django.http import HttpResponse, Http404, HttpResponseRedirect +from django.urls import reverse +from django.template import loader +from django.views import generic +from django.shortcuts import get_object_or_404, render +from list_posts.models import Post + + +class PostView(generic.DetailView): + model = Post + template_name = 'list_posts/post.html' def index(request): - return HttpResponse("Your posts will be posted here") + latest_posts_list = Post.objects.all()[:10] + template = loader.get_template('list_posts/index.html') + context = { + 'latest_posts_list': latest_posts_list, + } + return HttpResponse(template.render(context, request)) + + +def vote(request, post_id): + post = get_object_or_404(Post, pk=post_id) + + try: + vote_type = request.POST['vote'] + except (KeyError, Post.DoesNotExist): + # Redisplay the question voting form. + return render(request, 'list_posts/post.html', { + 'post': post, + 'error_message': "You didn't select a choice.", + }) + else: + if vote_type == 'upvote': + post.up_votes += 1 + elif vote_type == 'downvote': + post.down_votes += 1 + post.save() + return HttpResponseRedirect(reverse('list_posts:post', args=(post_id,))) def list_posts(request): From 38c8f481a529832f4ece23bdf2ac049037171f9c Mon Sep 17 00:00:00 2001 From: Ivopires Date: Tue, 18 Sep 2018 22:48:31 +0100 Subject: [PATCH 03/15] Starting models and views tests --- .../migrations/0002_post_pub_date.py | 22 ++++++++++++ .../list_posts/migrations/0003_post_score.py | 20 +++++++++++ challenge/list_posts/models.py | 14 ++++---- .../list_posts/static/list_posts/style.css | 3 ++ .../templates/list_posts/index.html | 12 ++++--- .../list_posts/templates/list_posts/post.html | 8 +++-- .../templates/list_posts/results.html | 10 ++++++ challenge/list_posts/tests.py | 6 ---- challenge/list_posts/tests/__init__.py | 9 +++++ challenge/list_posts/tests/test_model.py | 34 +++++++++++++++++++ challenge/list_posts/tests/test_view.py | 7 ++++ challenge/list_posts/urls.py | 2 +- challenge/list_posts/views.py | 34 ++++++++++++------- 13 files changed, 148 insertions(+), 33 deletions(-) create mode 100644 challenge/list_posts/migrations/0002_post_pub_date.py create mode 100644 challenge/list_posts/migrations/0003_post_score.py create mode 100644 challenge/list_posts/static/list_posts/style.css create mode 100644 challenge/list_posts/templates/list_posts/results.html delete mode 100644 challenge/list_posts/tests.py create mode 100644 challenge/list_posts/tests/__init__.py create mode 100644 challenge/list_posts/tests/test_model.py create mode 100644 challenge/list_posts/tests/test_view.py diff --git a/challenge/list_posts/migrations/0002_post_pub_date.py b/challenge/list_posts/migrations/0002_post_pub_date.py new file mode 100644 index 0000000..e1981b4 --- /dev/null +++ b/challenge/list_posts/migrations/0002_post_pub_date.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.15 on 2018-09-18 14:05 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('list_posts', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='post', + name='pub_date', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date Published'), + preserve_default=False, + ), + ] diff --git a/challenge/list_posts/migrations/0003_post_score.py b/challenge/list_posts/migrations/0003_post_score.py new file mode 100644 index 0000000..4614093 --- /dev/null +++ b/challenge/list_posts/migrations/0003_post_score.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.15 on 2018-09-18 19:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('list_posts', '0002_post_pub_date'), + ] + + operations = [ + migrations.AddField( + model_name='post', + name='score', + field=models.FloatField(default=0, verbose_name='Post Score'), + ), + ] diff --git a/challenge/list_posts/models.py b/challenge/list_posts/models.py index 8d3545b..f9401df 100644 --- a/challenge/list_posts/models.py +++ b/challenge/list_posts/models.py @@ -2,15 +2,18 @@ from __future__ import unicode_literals import numpy as np +from math import sqrt from django.db import models from django.utils.encoding import python_2_unicode_compatible @python_2_unicode_compatible class Post(models.Model): + pub_date = models.DateTimeField('Date Published') post_text = models.CharField('Post content', max_length=500) up_votes = models.PositiveIntegerField('Up votes', default=0) down_votes = models.PositiveIntegerField('Down votes', default=0) + score = models.FloatField('Post Score', default=0) def __str__(self): return self.post_text @@ -30,14 +33,11 @@ def compute_score(self): if self.up_votes == 0: return 0 else: - p_hat = np.float64(self.up_votes / total_votes) + p_hat = np.float64(self.up_votes) / total_votes z = np.float64(1.96) - lower_bound = [(p_hat + (z * z / (2 * total_votes)) - - (z * np.sqrt(p_hat * (1 - p_hat) + z * z / - (4 * total_votes))) / total_votes) / - (1 + (z * z / total_votes))] + lower_bound = (((p_hat + z * z / (2 * total_votes)) - z * sqrt( + (p_hat * (1 - p_hat) + z * z / + (4 * total_votes)) / total_votes)) / (1 + z * z / total_votes)) return lower_bound - - score = property(compute_score) diff --git a/challenge/list_posts/static/list_posts/style.css b/challenge/list_posts/static/list_posts/style.css new file mode 100644 index 0000000..4a1f590 --- /dev/null +++ b/challenge/list_posts/static/list_posts/style.css @@ -0,0 +1,3 @@ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} diff --git a/challenge/list_posts/templates/list_posts/index.html b/challenge/list_posts/templates/list_posts/index.html index d796751..362212a 100644 --- a/challenge/list_posts/templates/list_posts/index.html +++ b/challenge/list_posts/templates/list_posts/index.html @@ -1,9 +1,13 @@ +{% load static %} + + + {% if latest_posts_list %} -
    + {% else %} diff --git a/challenge/list_posts/templates/list_posts/post.html b/challenge/list_posts/templates/list_posts/post.html index 04b8ed6..8e6c153 100644 --- a/challenge/list_posts/templates/list_posts/post.html +++ b/challenge/list_posts/templates/list_posts/post.html @@ -1,9 +1,13 @@ +{% load static %} + + +

    Post with id {{post.id}}

    {{post.post_text}}

    -

    Results:{{post.up_votes}} vote{{post.up_votes|pluralize}} -- {{post.down_votes}} - vote{{post.down_votes|pluralize}}

    +

    Results: {{post.up_votes}} Up vote{{post.up_votes|pluralize}} -- + {{post.down_votes}} Down vote{{post.down_votes|pluralize}} with a score of {{post.score}}

    {% if error_message %}

    {{error_message}}

    {% endif %} diff --git a/challenge/list_posts/templates/list_posts/results.html b/challenge/list_posts/templates/list_posts/results.html new file mode 100644 index 0000000..17c9f1f --- /dev/null +++ b/challenge/list_posts/templates/list_posts/results.html @@ -0,0 +1,10 @@ +{% load static %} + + + +

    {{ post.post_text }}

    + +

    Results: {{post.up_votes}} Up vote{{post.up_votes|pluralize}} -- + {{post.down_votes}} Down vote{{post.down_votes|pluralize}} with a score of {{post.score}}

    + +Go home? diff --git a/challenge/list_posts/tests.py b/challenge/list_posts/tests.py deleted file mode 100644 index 5982e6b..0000000 --- a/challenge/list_posts/tests.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.test import TestCase - -# Create your tests here. diff --git a/challenge/list_posts/tests/__init__.py b/challenge/list_posts/tests/__init__.py new file mode 100644 index 0000000..ab0a59b --- /dev/null +++ b/challenge/list_posts/tests/__init__.py @@ -0,0 +1,9 @@ +import pkgutil +import unittest + +for loader, module_name, is_pkg in pkgutil.walk_packages(__path__): + module = loader.find_module(module_name).load_module(module_name) + for name in dir(module): + obj = getattr(module, name) + if isinstance(obj, type) and issubclass(obj, unittest.case.TestCase): + exec ('%s = obj' % obj.__name__) diff --git a/challenge/list_posts/tests/test_model.py b/challenge/list_posts/tests/test_model.py new file mode 100644 index 0000000..30ad138 --- /dev/null +++ b/challenge/list_posts/tests/test_model.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.test import TestCase +from list_posts.models import Post +from django.utils import timezone +from django.urls import reverse + + +def createPost(post_text, up_votes, down_votes): + """ + Create a post with the given 'post_text' and published with the given + number of up_votes and down_votes, which will aid in the assessment of the + overall score. + """ + now = timezone.now() + post = Post.objects.create( + pub_date=now, + post_text=post_text, + up_votes=up_votes, + down_votes=down_votes) + + +class PostModelTests(TestCase): + + def test_score_with_no_upvotes(self): + """ + The score property of the Post model should be equal to 0 when there + is any upvote + """ + post = Post( + post_text='Post with no upvotes.', up_votes=0, down_votes=10) + score = post.compute_score() + self.assertEqual(score, 0) diff --git a/challenge/list_posts/tests/test_view.py b/challenge/list_posts/tests/test_view.py new file mode 100644 index 0000000..bd297f1 --- /dev/null +++ b/challenge/list_posts/tests/test_view.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.test import TestCase +from list_posts.models import Post +from django.utils import timezone +from django.urls import reverse diff --git a/challenge/list_posts/urls.py b/challenge/list_posts/urls.py index 9b37a45..dbfeb6b 100644 --- a/challenge/list_posts/urls.py +++ b/challenge/list_posts/urls.py @@ -4,7 +4,7 @@ app_name = 'list_posts' urlpatterns = [ url(r'^$', views.index, name='index'), - url(r'^(?P[0-9]+)/$', views.PostView.as_view(), name='post'), + url(r'^(?P[0-9]+)/$', views.PostDetailView.as_view(), name='post'), url(r'^vote/(?P[0-9]+)/$', views.vote, name='vote'), url(r'^upvote/(?P[0-9]+)/$', views.up_vote, name='up_vote'), url(r'^downvote/(?P[0-9]+)/$', views.down_vote, name='down_vote'), diff --git a/challenge/list_posts/views.py b/challenge/list_posts/views.py index ccccd6b..5e3ea45 100644 --- a/challenge/list_posts/views.py +++ b/challenge/list_posts/views.py @@ -10,18 +10,15 @@ from list_posts.models import Post -class PostView(generic.DetailView): +class PostDetailView(generic.DetailView): model = Post template_name = 'list_posts/post.html' def index(request): - latest_posts_list = Post.objects.all()[:10] - template = loader.get_template('list_posts/index.html') - context = { - 'latest_posts_list': latest_posts_list, - } - return HttpResponse(template.render(context, request)) + latest_posts_list = Post.objects.order_by('-pub_date') + context = {'latest_posts_list': latest_posts_list} + return render(request, 'list_posts/index.html', context=context) def vote(request, post_id): @@ -33,26 +30,37 @@ def vote(request, post_id): # Redisplay the question voting form. return render(request, 'list_posts/post.html', { 'post': post, - 'error_message': "You didn't select a choice.", + 'error_message': "You didn't voted.", }) else: if vote_type == 'upvote': post.up_votes += 1 elif vote_type == 'downvote': post.down_votes += 1 + post.score = post.compute_score() post.save() return HttpResponseRedirect(reverse('list_posts:post', args=(post_id,))) def list_posts(request): - return HttpResponse("Here are the posts listed by top rating") + ordered_posts_list = Post.objects.order_by('-score') + context = {'latest_posts_list': ordered_posts_list} + return render(request, 'list_posts/index.html', context=context) def up_vote(request, post_id): - return HttpResponse( - "You just upvoted the post with the ID {}".format(post_id)) + post = get_object_or_404(Post, pk=post_id) + post.up_votes += 1 + post.score = post.compute_score() + post.save() + + return render(request, 'list_posts/results.html', {'post': post}) def down_vote(request, post_id): - return HttpResponse( - "You just downvoted the post with the ID {}".format(post_id)) + post = get_object_or_404(Post, pk=post_id) + post.down_votes += 1 + post.score = post.compute_score() + post.save() + + return render(request, 'list_posts/results.html', {'post': post}) From a39af4ecdde77f50fec69aadf9d9dbe335be3eca Mon Sep 17 00:00:00 2001 From: Ivopires Date: Thu, 20 Sep 2018 21:08:06 +0200 Subject: [PATCH 04/15] Added final tests and README --- challenge/list_posts/models.py | 8 +- challenge/list_posts/tests/test_model.py | 69 ++++++++++++++---- challenge/list_posts/tests/test_view.py | 93 ++++++++++++++++++++++++ challenge/list_posts/views.py | 10 ++- 4 files changed, 162 insertions(+), 18 deletions(-) diff --git a/challenge/list_posts/models.py b/challenge/list_posts/models.py index f9401df..ad7214c 100644 --- a/challenge/list_posts/models.py +++ b/challenge/list_posts/models.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import numpy as np +from django.utils import timezone from math import sqrt from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -9,7 +10,7 @@ @python_2_unicode_compatible class Post(models.Model): - pub_date = models.DateTimeField('Date Published') + pub_date = models.DateTimeField('Date Published', default=timezone.now()) post_text = models.CharField('Post content', max_length=500) up_votes = models.PositiveIntegerField('Up votes', default=0) down_votes = models.PositiveIntegerField('Down votes', default=0) @@ -21,11 +22,12 @@ def __str__(self): def compute_score(self): """ The compute_score function will compute the Wilson-score Interval, - depending on the number of up/down votes and the total number of votes. + which calculation depends on the number of up/down votes and the total + number of votes. Where: - p_hat - is the fraction of up votes out of total votes - total_votes - is the total number of votes - - z - is the normal distribution, which, in order to have 0.95 + - z - is the normal distribution, which, in order to have 95% of confidence, has to be equal to 1.96 """ diff --git a/challenge/list_posts/tests/test_model.py b/challenge/list_posts/tests/test_model.py index 30ad138..151822a 100644 --- a/challenge/list_posts/tests/test_model.py +++ b/challenge/list_posts/tests/test_model.py @@ -7,20 +7,6 @@ from django.urls import reverse -def createPost(post_text, up_votes, down_votes): - """ - Create a post with the given 'post_text' and published with the given - number of up_votes and down_votes, which will aid in the assessment of the - overall score. - """ - now = timezone.now() - post = Post.objects.create( - pub_date=now, - post_text=post_text, - up_votes=up_votes, - down_votes=down_votes) - - class PostModelTests(TestCase): def test_score_with_no_upvotes(self): @@ -32,3 +18,58 @@ def test_score_with_no_upvotes(self): post_text='Post with no upvotes.', up_votes=0, down_votes=10) score = post.compute_score() self.assertEqual(score, 0) + + def test_score_with_no_downvotes(self): + """ + The score property of the Post model should be different to 0 when + there is any downvote + """ + post = Post(post_text='Post with no down.', up_votes=10, down_votes=0) + score = post.compute_score() + self.assertNotEqual(score, 0) + + def test_score_from_posts_with_same_ratio(self): + """ + The score property of the Post model should be greater on a Post with + 1000/1000 up/down votes than on a Post with 100/100 up/down votes, + despite of having the same ratio of up/down votes. + """ + post_100 = Post( + post_text='Post with 100/100 votes', up_votes=100, down_votes=100) + score_100 = post_100.compute_score() + + post_1000 = Post( + post_text='Post with 1000/1000 votes', + up_votes=1000, + down_votes=1000) + score_1000 = post_1000.compute_score() + + self.assertGreater(score_1000, score_100) + + def test_score_from_posts_with_different_scores(self): + """ + The score property of the Post model should be greater on a Post with + 1000/100 up/down votes than on a Post with 10/100 up/down votes. + """ + post_1000 = Post( + post_text='Post with 1000/100 votes', up_votes=1000, down_votes=100) + score_1000 = post_1000.compute_score() + + post_10 = Post( + post_text='Post with 10/100 votes', up_votes=10, down_votes=100) + score_10 = post_10.compute_score() + + self.assertGreater(score_1000, score_10) + + def test_score_up_down_votes_when_post_is_created(self): + """ + All the three properties (up/down votes and score) of the Post model + should be equal to 0 on a Post that was recently created, and when there + wasn't specified a different number of up/down votes than the default + value (i.e., 0) + """ + post_without_votes = Post(post_text='Post without votes.') + + self.assertEqual(post_without_votes.up_votes, 0) + self.assertEqual(post_without_votes.down_votes, 0) + self.assertEqual(post_without_votes.score, 0) diff --git a/challenge/list_posts/tests/test_view.py b/challenge/list_posts/tests/test_view.py index bd297f1..b467ee9 100644 --- a/challenge/list_posts/tests/test_view.py +++ b/challenge/list_posts/tests/test_view.py @@ -1,7 +1,100 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import datetime from django.test import TestCase from list_posts.models import Post from django.utils import timezone from django.urls import reverse + + +def create_post(post_text, days, up_votes=0, down_votes=0): + """ + Create a post with the given 'post_text' and published with the given + number of up_votes and down_votes, and published the given + number of 'days' offset to now (negative for posts published in the past + and positive for posts that have yet to be published). + """ + date = timezone.now() + datetime.timedelta(days=days) + post = Post.objects.create( + pub_date=date, + post_text=post_text, + up_votes=up_votes, + down_votes=down_votes) + return post + + +class PostIndexViewTest(TestCase): + + def test_no_posts(self): + """ + If no posts exist, an appropriate message is displayed. + """ + response = self.client.get(reverse('list_posts:index')) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'No posts are available.') + self.assertQuerysetEqual(response.context['latest_posts_list'], []) + + def test_past_post(self): + """ + Posts with a pub_date in the past are displayed on the index page. + """ + create_post(post_text='Past post.', days=-30) + response = self.client.get(reverse('list_posts:index')) + self.assertQuerysetEqual(response.context['latest_posts_list'], + ['']) + + def test_future_post(self): + """ + Posts with a pub_date in the future aren't displayed on the index + page. + """ + create_post(post_text='Future post.', days=30) + response = self.client.get(reverse('list_posts:index')) + self.assertContains(response, 'No posts are available.') + self.assertQuerysetEqual(response.context['latest_posts_list'], []) + + def test_future_post_and_past_post(self): + """ + Even if both, past and future, posts exist, only past posts are + displayed. + """ + create_post(post_text='Past post.', days=-30) + create_post(post_text='Future post.', days=30) + response = self.client.get(reverse('list_posts:index')) + self.assertQuerysetEqual(response.context['latest_posts_list'], + ['']) + + def test_two_past_posts(self): + """ + The posts index page may display multiple questions. + """ + create_post(post_text='Past post 2.', days=-5) + create_post(post_text='Past post 1.', days=-30) + response = self.client.get(reverse('list_posts:index')) + self.assertQuerysetEqual( + response.context['latest_posts_list'], + ['', '']) + + +class PostDetailViewTest(TestCase): + + def test_future_post(self): + """ + The detail view of a post with a pub_date in the future returns a + 404 not found + """ + future_post = create_post(post_text='Future post.', days=5) + url = reverse('list_posts:post', args=(future_post.id,)) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def test_past_post(self): + """ + The detail view of a post with a pub_date in the past displays the + post's text. + """ + past_post = create_post(post_text='Past post.', days=-5) + url = reverse('list_posts:post', args=(past_post.id,)) + response = self.client.get(url) + self.assertContains(response, past_post.post_text) diff --git a/challenge/list_posts/views.py b/challenge/list_posts/views.py index 5e3ea45..4be8243 100644 --- a/challenge/list_posts/views.py +++ b/challenge/list_posts/views.py @@ -7,6 +7,7 @@ from django.template import loader from django.views import generic from django.shortcuts import get_object_or_404, render +from django.utils import timezone from list_posts.models import Post @@ -14,9 +15,16 @@ class PostDetailView(generic.DetailView): model = Post template_name = 'list_posts/post.html' + def get_queryset(self): + """ + Excludes any posts that aren't published yet. + """ + return Post.objects.filter(pub_date__lte=timezone.now()) + def index(request): - latest_posts_list = Post.objects.order_by('-pub_date') + latest_posts_list = Post.objects.filter( + pub_date__lte=timezone.now()).order_by('-pub_date') context = {'latest_posts_list': latest_posts_list} return render(request, 'list_posts/index.html', context=context) From cabd661e068bd45e5a62f8239fa78e5c2a3c6735 Mon Sep 17 00:00:00 2001 From: Ivopires Date: Thu, 20 Sep 2018 21:10:43 +0200 Subject: [PATCH 05/15] Updated README --- README.md | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 35c11e6..6e83e14 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,46 @@ # ListPostsByRating -App developed in Python, using the Django library +App developed in Python, using the Django framework. + +# List Posts By Rating + +## How to run + +### Setup Django (version 1.11.15): + 1. Install pip (if you have not installed already): ![Guide](https://packaging.python.org/tutorials/installing-packages/) + 2. Install Django (version 1.11.15): + ```shell + pip install Django==1.11.15 + ``` + +### Populate db with Posts +```shell +python manage.py shell + +>>>from list_posts.models import Posts +>>>post = Post(post_text='Example text') +>>>post.save() +``` + +### Run tests +```shell +python manage.py test list_posts +``` + +### Run the project +To run project you must follow the following steps: + 1. ``` shell + python manage.py runserver + ``` + 2. Go to the following link - [127.0.0.1:8000](127.0.0.1:8000) + +There are four available endpoints: + ```shell + / + /upvote/:post_id + /downvote/:post_id + /posts/ + ``` + +The first endpoint is responsible to list the latest 10 posts, ordered by posting date, if by accident 'future' posts are added to the database, these will not show on the page. The second and third endpoints are responsible to give the up/down votes, respectively, to the post with the . Finally, the last endpoint, will show all the available posts, ordered by 'score', being the top-scored posts on the top and the less-scored posts on the bottom. + +To achieve a post score that would allow to order different posts with the same up/down votes ratio, as mentioned on the challenge's README, a Wilson-score Interval metric was used, which value depends solely on the number of up/down votes, total number of votes of the post and on the __z__ value used by the normal distribution (which depends on the degree of confidence used). From a5d9bca51705467e430762427806d12fc2a3f2c6 Mon Sep 17 00:00:00 2001 From: Ivopires Date: Thu, 20 Sep 2018 21:20:33 +0200 Subject: [PATCH 06/15] Update README - fixing the link issue --- README.md | 20 ++++++++++---------- challenge/list_posts/models.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6e83e14..3256093 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ App developed in Python, using the Django framework. ## How to run ### Setup Django (version 1.11.15): - 1. Install pip (if you have not installed already): ![Guide](https://packaging.python.org/tutorials/installing-packages/) + 1. Install pip (if you have not installed already): [Guide](https://packaging.python.org/tutorials/installing-packages/) 2. Install Django (version 1.11.15): - ```shell + ``` pip install Django==1.11.15 ``` ### Populate db with Posts -```shell +``` python manage.py shell >>>from list_posts.models import Posts @@ -28,17 +28,17 @@ python manage.py test list_posts ### Run the project To run project you must follow the following steps: - 1. ``` shell + 1. ``` python manage.py runserver ``` - 2. Go to the following link - [127.0.0.1:8000](127.0.0.1:8000) + 2. Go to the following link - [127.0.0.1:8000](http://127.0.0.1:8000) There are four available endpoints: - ```shell - / - /upvote/:post_id - /downvote/:post_id - /posts/ + ``` + / + /upvote/:post_id + /downvote/:post_id + /posts/ ``` The first endpoint is responsible to list the latest 10 posts, ordered by posting date, if by accident 'future' posts are added to the database, these will not show on the page. The second and third endpoints are responsible to give the up/down votes, respectively, to the post with the . Finally, the last endpoint, will show all the available posts, ordered by 'score', being the top-scored posts on the top and the less-scored posts on the bottom. diff --git a/challenge/list_posts/models.py b/challenge/list_posts/models.py index ad7214c..bbce9ff 100644 --- a/challenge/list_posts/models.py +++ b/challenge/list_posts/models.py @@ -10,7 +10,7 @@ @python_2_unicode_compatible class Post(models.Model): - pub_date = models.DateTimeField('Date Published', default=timezone.now()) + pub_date = models.DateTimeField('Date Published', default=timezone.now) post_text = models.CharField('Post content', max_length=500) up_votes = models.PositiveIntegerField('Up votes', default=0) down_votes = models.PositiveIntegerField('Down votes', default=0) From c62ce6580a6bdfedd07a63927c7d12c42db80d5a Mon Sep 17 00:00:00 2001 From: Ivopires Date: Thu, 20 Sep 2018 21:22:45 +0200 Subject: [PATCH 07/15] Update README --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3256093..3e3f249 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ App developed in Python, using the Django framework. ### Setup Django (version 1.11.15): 1. Install pip (if you have not installed already): [Guide](https://packaging.python.org/tutorials/installing-packages/) - 2. Install Django (version 1.11.15): + 2. Install Django (version 1.11.15):
    ``` pip install Django==1.11.15 ``` @@ -22,7 +22,7 @@ python manage.py shell ``` ### Run tests -```shell +``` python manage.py test list_posts ``` @@ -35,10 +35,10 @@ To run project you must follow the following steps: There are four available endpoints: ``` - / - /upvote/:post_id - /downvote/:post_id - /posts/ + /
    + /upvote/:post_id
    + /downvote/:post_id
    + /posts/
    ``` The first endpoint is responsible to list the latest 10 posts, ordered by posting date, if by accident 'future' posts are added to the database, these will not show on the page. The second and third endpoints are responsible to give the up/down votes, respectively, to the post with the . Finally, the last endpoint, will show all the available posts, ordered by 'score', being the top-scored posts on the top and the less-scored posts on the bottom. From d58de16fa3833f6ae7fc9a29326960714e003c29 Mon Sep 17 00:00:00 2001 From: Ivopires Date: Thu, 20 Sep 2018 21:27:44 +0200 Subject: [PATCH 08/15] Update README.md --- README.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3e3f249..93192d7 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,13 @@ App developed in Python, using the Django framework. ### Setup Django (version 1.11.15): 1. Install pip (if you have not installed already): [Guide](https://packaging.python.org/tutorials/installing-packages/) - 2. Install Django (version 1.11.15):
    + 2. Install Django (version 1.11.15): ``` - pip install Django==1.11.15 + pip install Django==1.11.15 ``` ### Populate db with Posts + ``` python manage.py shell @@ -22,24 +23,29 @@ python manage.py shell ``` ### Run tests + ``` python manage.py test list_posts ``` ### Run the project To run project you must follow the following steps: - 1. ``` - python manage.py runserver - ``` + + 1. Start the server: + + ``` + python manage.py runserver + ``` + 2. Go to the following link - [127.0.0.1:8000](http://127.0.0.1:8000) There are four available endpoints: - ``` - /
    - /upvote/:post_id
    - /downvote/:post_id
    - /posts/
    - ``` +``` +/ +/upvote/:post_id +/downvote/:post_id +/posts/ +``` The first endpoint is responsible to list the latest 10 posts, ordered by posting date, if by accident 'future' posts are added to the database, these will not show on the page. The second and third endpoints are responsible to give the up/down votes, respectively, to the post with the . Finally, the last endpoint, will show all the available posts, ordered by 'score', being the top-scored posts on the top and the less-scored posts on the bottom. From 66c7664048711077ca709e228c34c96d381fcdd5 Mon Sep 17 00:00:00 2001 From: Ivopires Date: Thu, 20 Sep 2018 21:32:44 +0200 Subject: [PATCH 09/15] Folder structure changed --- challenge/{challenge => }/__init__.py | 0 challenge/{challenge => }/settings.py | 0 challenge/{challenge => }/urls.py | 0 challenge/{challenge => }/wsgi.py | 0 {challenge/list_posts => list_posts}/__init__.py | 0 {challenge/list_posts => list_posts}/admin.py | 0 {challenge/list_posts => list_posts}/apps.py | 0 {challenge/list_posts => list_posts}/migrations/0001_initial.py | 0 .../list_posts => list_posts}/migrations/0002_post_pub_date.py | 0 .../list_posts => list_posts}/migrations/0003_post_score.py | 0 {challenge/list_posts => list_posts}/migrations/__init__.py | 0 {challenge/list_posts => list_posts}/models.py | 0 {challenge/list_posts => list_posts}/static/list_posts/style.css | 0 .../list_posts => list_posts}/templates/list_posts/index.html | 0 .../list_posts => list_posts}/templates/list_posts/post.html | 0 .../list_posts => list_posts}/templates/list_posts/results.html | 0 {challenge/list_posts => list_posts}/tests/__init__.py | 0 {challenge/list_posts => list_posts}/tests/test_model.py | 0 {challenge/list_posts => list_posts}/tests/test_view.py | 0 {challenge/list_posts => list_posts}/urls.py | 0 {challenge/list_posts => list_posts}/views.py | 0 challenge/manage.py => manage.py | 0 22 files changed, 0 insertions(+), 0 deletions(-) rename challenge/{challenge => }/__init__.py (100%) rename challenge/{challenge => }/settings.py (100%) rename challenge/{challenge => }/urls.py (100%) rename challenge/{challenge => }/wsgi.py (100%) rename {challenge/list_posts => list_posts}/__init__.py (100%) rename {challenge/list_posts => list_posts}/admin.py (100%) rename {challenge/list_posts => list_posts}/apps.py (100%) rename {challenge/list_posts => list_posts}/migrations/0001_initial.py (100%) rename {challenge/list_posts => list_posts}/migrations/0002_post_pub_date.py (100%) rename {challenge/list_posts => list_posts}/migrations/0003_post_score.py (100%) rename {challenge/list_posts => list_posts}/migrations/__init__.py (100%) rename {challenge/list_posts => list_posts}/models.py (100%) rename {challenge/list_posts => list_posts}/static/list_posts/style.css (100%) rename {challenge/list_posts => list_posts}/templates/list_posts/index.html (100%) rename {challenge/list_posts => list_posts}/templates/list_posts/post.html (100%) rename {challenge/list_posts => list_posts}/templates/list_posts/results.html (100%) rename {challenge/list_posts => list_posts}/tests/__init__.py (100%) rename {challenge/list_posts => list_posts}/tests/test_model.py (100%) rename {challenge/list_posts => list_posts}/tests/test_view.py (100%) rename {challenge/list_posts => list_posts}/urls.py (100%) rename {challenge/list_posts => list_posts}/views.py (100%) rename challenge/manage.py => manage.py (100%) diff --git a/challenge/challenge/__init__.py b/challenge/__init__.py similarity index 100% rename from challenge/challenge/__init__.py rename to challenge/__init__.py diff --git a/challenge/challenge/settings.py b/challenge/settings.py similarity index 100% rename from challenge/challenge/settings.py rename to challenge/settings.py diff --git a/challenge/challenge/urls.py b/challenge/urls.py similarity index 100% rename from challenge/challenge/urls.py rename to challenge/urls.py diff --git a/challenge/challenge/wsgi.py b/challenge/wsgi.py similarity index 100% rename from challenge/challenge/wsgi.py rename to challenge/wsgi.py diff --git a/challenge/list_posts/__init__.py b/list_posts/__init__.py similarity index 100% rename from challenge/list_posts/__init__.py rename to list_posts/__init__.py diff --git a/challenge/list_posts/admin.py b/list_posts/admin.py similarity index 100% rename from challenge/list_posts/admin.py rename to list_posts/admin.py diff --git a/challenge/list_posts/apps.py b/list_posts/apps.py similarity index 100% rename from challenge/list_posts/apps.py rename to list_posts/apps.py diff --git a/challenge/list_posts/migrations/0001_initial.py b/list_posts/migrations/0001_initial.py similarity index 100% rename from challenge/list_posts/migrations/0001_initial.py rename to list_posts/migrations/0001_initial.py diff --git a/challenge/list_posts/migrations/0002_post_pub_date.py b/list_posts/migrations/0002_post_pub_date.py similarity index 100% rename from challenge/list_posts/migrations/0002_post_pub_date.py rename to list_posts/migrations/0002_post_pub_date.py diff --git a/challenge/list_posts/migrations/0003_post_score.py b/list_posts/migrations/0003_post_score.py similarity index 100% rename from challenge/list_posts/migrations/0003_post_score.py rename to list_posts/migrations/0003_post_score.py diff --git a/challenge/list_posts/migrations/__init__.py b/list_posts/migrations/__init__.py similarity index 100% rename from challenge/list_posts/migrations/__init__.py rename to list_posts/migrations/__init__.py diff --git a/challenge/list_posts/models.py b/list_posts/models.py similarity index 100% rename from challenge/list_posts/models.py rename to list_posts/models.py diff --git a/challenge/list_posts/static/list_posts/style.css b/list_posts/static/list_posts/style.css similarity index 100% rename from challenge/list_posts/static/list_posts/style.css rename to list_posts/static/list_posts/style.css diff --git a/challenge/list_posts/templates/list_posts/index.html b/list_posts/templates/list_posts/index.html similarity index 100% rename from challenge/list_posts/templates/list_posts/index.html rename to list_posts/templates/list_posts/index.html diff --git a/challenge/list_posts/templates/list_posts/post.html b/list_posts/templates/list_posts/post.html similarity index 100% rename from challenge/list_posts/templates/list_posts/post.html rename to list_posts/templates/list_posts/post.html diff --git a/challenge/list_posts/templates/list_posts/results.html b/list_posts/templates/list_posts/results.html similarity index 100% rename from challenge/list_posts/templates/list_posts/results.html rename to list_posts/templates/list_posts/results.html diff --git a/challenge/list_posts/tests/__init__.py b/list_posts/tests/__init__.py similarity index 100% rename from challenge/list_posts/tests/__init__.py rename to list_posts/tests/__init__.py diff --git a/challenge/list_posts/tests/test_model.py b/list_posts/tests/test_model.py similarity index 100% rename from challenge/list_posts/tests/test_model.py rename to list_posts/tests/test_model.py diff --git a/challenge/list_posts/tests/test_view.py b/list_posts/tests/test_view.py similarity index 100% rename from challenge/list_posts/tests/test_view.py rename to list_posts/tests/test_view.py diff --git a/challenge/list_posts/urls.py b/list_posts/urls.py similarity index 100% rename from challenge/list_posts/urls.py rename to list_posts/urls.py diff --git a/challenge/list_posts/views.py b/list_posts/views.py similarity index 100% rename from challenge/list_posts/views.py rename to list_posts/views.py diff --git a/challenge/manage.py b/manage.py similarity index 100% rename from challenge/manage.py rename to manage.py From 103b59161da2513b490981b75d16977a3ed63786 Mon Sep 17 00:00:00 2001 From: Ivopires Date: Thu, 20 Sep 2018 22:10:15 +0200 Subject: [PATCH 10/15] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 93192d7..eb62d34 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,11 @@ App developed in Python, using the Django framework. pip install Django==1.11.15 ``` +### Load db scheme +``` +python manage.py migrate +``` + ### Populate db with Posts ``` From 59c233c092fa33300318aef274847029001d2cec Mon Sep 17 00:00:00 2001 From: Ivopires Date: Thu, 20 Sep 2018 22:12:25 +0200 Subject: [PATCH 11/15] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index eb62d34..880a2c6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ -# ListPostsByRating -App developed in Python, using the Django framework. - # List Posts By Rating +App developed in Python(2.7), using the Django(1.11.15) framework. ## How to run From b0fce1fe065b8b0bf5d33f59dee21eb5a809f276 Mon Sep 17 00:00:00 2001 From: Ivopires Date: Thu, 20 Sep 2018 22:21:57 +0200 Subject: [PATCH 12/15] Add travis config --- .travis.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..23f34ab --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: python +python: + - "2.7" +services: + - sql +env: + -DJANGO=1.11.15 DB=sql +before_script: + - python manage.py migrate +script: + - python manage.py test list_posts + From a8348b188df3f22b73dab4731c1770ca372c4cc5 Mon Sep 17 00:00:00 2001 From: Ivopires Date: Thu, 20 Sep 2018 22:26:59 +0200 Subject: [PATCH 13/15] Fix travis config --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 23f34ab..9e43fa0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,8 @@ python: - "2.7" services: - sql -env: - -DJANGO=1.11.15 DB=sql +install: +- pip install -q Django=1.11.15 before_script: - python manage.py migrate script: From 6644dcf13ac7b1cf389b62cecc8b021ffdbe3705 Mon Sep 17 00:00:00 2001 From: Ivopires Date: Thu, 20 Sep 2018 22:30:05 +0200 Subject: [PATCH 14/15] Fix travis config --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9e43fa0..3e0199a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,9 @@ language: python python: - "2.7" services: - - sql + - sqlite3 install: -- pip install -q Django=1.11.15 +- pip install -q Django==1.11.15 before_script: - python manage.py migrate script: From b2cbfb1df996cb75560039ab8acea301bb9760ad Mon Sep 17 00:00:00 2001 From: Ivopires Date: Thu, 20 Sep 2018 22:32:20 +0200 Subject: [PATCH 15/15] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 880a2c6..f698de8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.com/Ivopires/ListPostsByRating.svg?branch=challenge)](https://travis-ci.com/Ivopires/ListPostsByRating) + # List Posts By Rating App developed in Python(2.7), using the Django(1.11.15) framework.