commit cfaa2b2a5092bb2dab992d4d4482c5e38234fb94 Author: Gergely Polonkai Date: Thu Nov 26 13:03:46 2015 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..087c883 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/venv/ +.coverage +db.sqlite3 +/htmlcov/ diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md new file mode 100644 index 0000000..b06ed16 --- /dev/null +++ b/CONTRIBUTE.md @@ -0,0 +1,6 @@ +If you want to contribute, put your ideas on the issue board; the +project leads will mark it as a feature, and discussion can start if +it is required or not. + +If you find a bug in the code somewhere, feel free to fix it and issue +a pull request against master. The same goes for features. \ No newline at end of file diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d3b07e3 --- /dev/null +++ b/COPYING @@ -0,0 +1,13 @@ +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public +License along with this program. If not, see +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f232b5d --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +100(ish) words challenge +======================== + +This is the main project for 100(ish)word challenge, a challenge for +artists of different kinds. + +The goal of the site is to present a word for the users every day, and +let them create something based on that word, within a given time +limit. They can then share their work via the site, so other users can +vote for it (if it represents the word by any means). + +The project aims to run on Heroku, but testing is done for both Python +2.7 (because of Heroku) and Python 3.4 (because we can). \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..c8595ec --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wordchallenge.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/wordchallenge/__init__.py b/wordchallenge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wordchallenge/settings.py b/wordchallenge/settings.py new file mode 100644 index 0000000..10f97e6 --- /dev/null +++ b/wordchallenge/settings.py @@ -0,0 +1,103 @@ +""" +Django settings for wordchallenge project. + +Generated by 'django-admin startproject' using Django 1.8.7. + +For more information on this file, see +https://docs.djangoproject.com/en/1.8/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.8/ref/settings/ +""" + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import os + +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.8/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '8$#!)33w0dfn#!ttb2p+)0a*i)n1&q6gwecasfic0+^idz4%qk' + +# 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', + 'words', +) + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', +) + +ROOT_URLCONF = 'wordchallenge.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 = 'wordchallenge.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.8/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Internationalization +# https://docs.djangoproject.com/en/1.8/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.8/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/wordchallenge/urls.py b/wordchallenge/urls.py new file mode 100644 index 0000000..1ff37bb --- /dev/null +++ b/wordchallenge/urls.py @@ -0,0 +1,21 @@ +"""wordchallenge URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.8/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. Add an import: from blog import urls as blog_urls + 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) +""" +from django.conf.urls import include, url +from django.contrib import admin + +urlpatterns = [ + url(r'^admin/', include(admin.site.urls)), +] diff --git a/wordchallenge/wsgi.py b/wordchallenge/wsgi.py new file mode 100644 index 0000000..7dfa743 --- /dev/null +++ b/wordchallenge/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for wordchallenge 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.8/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wordchallenge.settings") + +application = get_wsgi_application() diff --git a/words/__init__.py b/words/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/words/migrations/0001_initial.py b/words/migrations/0001_initial.py new file mode 100644 index 0000000..ca60ca6 --- /dev/null +++ b/words/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Word', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ], + ), + migrations.CreateModel( + name='WordTranslation', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('language', models.CharField(db_index=True, max_length=5)), + ('translation', models.CharField(null=True, max_length=100)), + ('added_at', models.DateTimeField(default=django.utils.timezone.now)), + ('added_by', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ('word', models.ForeignKey(related_name='translations', to='words.Word')), + ], + ), + migrations.AlterUniqueTogether( + name='wordtranslation', + unique_together=set([('language', 'translation'), ('word', 'language')]), + ), + ] diff --git a/words/migrations/__init__.py b/words/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/words/models.py b/words/models.py new file mode 100644 index 0000000..2d34dec --- /dev/null +++ b/words/models.py @@ -0,0 +1,49 @@ +from django.conf import settings +from django.contrib.auth.models import User +from django.db import models +from django.utils import timezone +from django.utils.translation import get_language + +class Word(models.Model): + def translation(self, language): + try: + return self.translations.get(language=language) + except WordTranslation.DoesNotExist: + return None + + def __str__(self): + try: + return self.translations.get(language=get_language()).translation + except WordTranslation.DoesNotExist: + pass + + try: + return self.translations \ + .get(language=settings.LANGUAGE_CODE).translation + except WordTranslation.DoesNotExist: + pass + + return "" + +class WordTranslation(models.Model): + word = models.ForeignKey(Word, related_name='translations') + language = models.CharField(max_length=5, db_index=True) + translation = models.CharField(max_length=100, null=True, blank=False) + added_by = models.ForeignKey(User) + added_at = models.DateTimeField(default=timezone.now) + + def clean(self): + from django.core.exceptions import ValidationError + + if self.translation is None or self.translation == '': + raise ValidationError('translation must not be empty', + code='translation-empty') + + def __str__(self): + return self.translation + + class Meta: + unique_together = ( + ('word', 'language'), + ('language', 'translation'), + ) diff --git a/words/tests.py b/words/tests.py new file mode 100644 index 0000000..1a1f582 --- /dev/null +++ b/words/tests.py @@ -0,0 +1,56 @@ +from django.contrib.auth.models import User +from django.core.exceptions import ValidationError +from django.utils.translation import activate +from django.test import TestCase + +from .models import Word, WordTranslation + +class WordTest(TestCase): + def setUp(self): + user = User.objects.create_user(username='test', password='test') + self.word1 = Word.objects.create() + self.translation1 = WordTranslation.objects.create( + word=self.word1, + language='en-us', + translation='color', + added_by=user) + self.translation2 = WordTranslation.objects.create( + word=self.word1, + language='en-gb', + translation='colour', + added_by=user) + self.translation3 = WordTranslation.objects.create( + word=self.word1, + language='hu-hu', + translation='szín', + added_by=user) + + def test_word_str(self): + with self.settings(LANGUAGE_CODE='en-us'): + self.assertEquals("color", self.word1.__str__()) + + with self.settings(LANGUAGE_CODE='en-gb'): + self.assertEquals('colour', self.word1.__str__()) + + activate('hu-hu') + self.assertEquals('szín', self.word1.__str__()) + + with self.settings(LANGUAGE_CODE='es-es'): + activate('is-is') + self.assertEquals('', self.word1.__str__()) + + def test_word_translation(self): + self.assertEquals('color', self.word1.translation('en-us').translation) + self.assertEquals('colour', self.word1.translation('en-gb').translation) + self.assertIsNone(self.word1.translation('is-is')) + + def test_translation_validation(self): + word = WordTranslation() + + with self.assertRaises(ValidationError) as ctx: + word.clean() + + self.assertEquals('translation-empty', ctx.exception.code) + + def test_translation_str(self): + self.assertEquals('color', self.translation1.__str__())