From 6a6d061e92f62a75ae4b775a49a13bf997b1c38d Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Tue, 8 Nov 2016 13:10:49 -0800 Subject: [PATCH] Added django sample --- samples/development-frameworks/django/LICENSE | 21 + .../development-frameworks/django/Procfile | 1 + .../development-frameworks/django/README.md | 87 + .../django/bootcamp/__init__.py | 0 .../django/bootcamp/activities/__init__.py | 0 .../activities/migrations/0001_initial.py | 58 + .../activities/migrations/__init__.py | 0 .../django/bootcamp/activities/models.py | 142 ++ .../activities/static/css/notifications.css | 33 + .../activities/static/js/notifications.js | 42 + .../activities/last_notifications.html | 15 + .../templates/activities/notifications.html | 30 + .../django/bootcamp/activities/tests.py | 3 + .../django/bootcamp/activities/views.py | 42 + .../django/bootcamp/articles/__init__.py | 0 .../django/bootcamp/articles/forms.py | 20 + .../articles/migrations/0001_initial.py | 73 + .../bootcamp/articles/migrations/__init__.py | 0 .../django/bootcamp/articles/models.py | 116 ++ .../bootcamp/articles/static/css/articles.css | 103 + .../bootcamp/articles/static/js/articles.js | 56 + .../articles/templates/articles/article.html | 19 + .../articles/templates/articles/articles.html | 41 + .../articles/templates/articles/drafts.html | 42 + .../articles/templates/articles/edit.html | 39 + .../templates/articles/partial_article.html | 27 + .../articles/partial_article_comment.html | 12 + .../articles/partial_article_comments.html | 23 + .../articles/templates/articles/write.html | 55 + .../bootcamp/articles/tests/__init__.py | 0 .../bootcamp/articles/tests/test_articles.py | 39 + .../django/bootcamp/articles/urls.py | 16 + .../django/bootcamp/articles/views.py | 144 ++ .../bootcamp/authentication/__init__.py | 0 .../django/bootcamp/authentication/forms.py | 92 + .../authentication/migrations/0001_initial.py | 32 + .../authentication/migrations/__init__.py | 0 .../django/bootcamp/authentication/models.py | 132 ++ .../authentication/static/css/signup.css | 41 + .../templates/authentication/signup.html | 30 + .../django/bootcamp/authentication/tests.py | 3 + .../django/bootcamp/authentication/views.py | 31 + .../django/bootcamp/core/__init__.py | 0 .../django/bootcamp/core/forms.py | 69 + .../bootcamp/core/migrations/__init__.py | 0 .../django/bootcamp/core/models.py | 3 + .../django/bootcamp/core/static/css/cover.css | 39 + .../bootcamp/core/static/css/network.css | 7 + .../bootcamp/core/static/css/profile.css | 17 + .../django/bootcamp/core/static/js/picture.js | 35 + .../bootcamp/core/templates/core/cover.html | 46 + .../bootcamp/core/templates/core/network.html | 50 + .../templates/core/partial_settings_menu.html | 6 + .../core/templates/core/password.html | 49 + .../bootcamp/core/templates/core/picture.html | 75 + .../bootcamp/core/templates/core/profile.html | 55 + .../core/templates/core/settings.html | 62 + .../django/bootcamp/core/tests.py | 3 + .../django/bootcamp/core/views.py | 158 ++ .../django/bootcamp/decorators.py | 13 + .../django/bootcamp/feeds/__init__.py | 0 .../bootcamp/feeds/migrations/0001_initial.py | 36 + .../bootcamp/feeds/migrations/__init__.py | 0 .../django/bootcamp/feeds/models.py | 73 + .../bootcamp/feeds/static/css/feeds.css | 167 ++ .../django/bootcamp/feeds/static/js/feeds.js | 296 +++ .../bootcamp/feeds/templates/feeds/feed.html | 22 + .../bootcamp/feeds/templates/feeds/feeds.html | 70 + .../feeds/templates/feeds/partial_feed.html | 44 + .../feeds/partial_feed_comments.html | 22 + .../django/bootcamp/feeds/tests.py | 3 + .../django/bootcamp/feeds/urls.py | 19 + .../django/bootcamp/feeds/views.py | 208 ++ .../bootcamp/locale/es/LC_MESSAGES/django.po | 331 ++++ .../bootcamp/locale/jp/LC_MESSAGES/django.po | 333 ++++ .../locale/pt_BR/LC_MESSAGES/django.po | 334 ++++ .../locale/zh_CN/LC_MESSAGES/django.po | 331 ++++ .../django/bootcamp/messenger/__init__.py | 0 .../messenger/migrations/0001_initial.py | 37 + .../bootcamp/messenger/migrations/__init__.py | 0 .../django/bootcamp/messenger/models.py | 56 + .../messenger/static/css/messages.css | 74 + .../messenger/static/js/check_messages.js | 15 + .../bootcamp/messenger/static/js/messages.js | 16 + .../messenger/static/js/messages.typehead.js | 32 + .../templates/messenger/base_messages.html | 24 + .../messenger/templates/messenger/inbox.html | 28 + .../includes/partial_conversations.html | 23 + .../messenger/includes/partial_message.html | 14 + .../messenger/templates/messenger/new.html | 31 + .../django/bootcamp/messenger/tests.py | 3 + .../django/bootcamp/messenger/urls.py | 15 + .../django/bootcamp/messenger/views.py | 127 ++ .../django/bootcamp/questions/__init__.py | 0 .../django/bootcamp/questions/forms.py | 32 + .../questions/migrations/0001_initial.py | 83 + .../bootcamp/questions/migrations/__init__.py | 0 .../django/bootcamp/questions/models.py | 147 ++ .../questions/static/css/questions.css | 125 ++ .../bootcamp/questions/static/js/questions.js | 93 + .../questions/templates/questions/ask.html | 35 + .../templates/questions/partial_answer.html | 29 + .../templates/questions/partial_question.html | 39 + .../templates/questions/question.html | 64 + .../templates/questions/questions.html | 32 + .../django/bootcamp/questions/tests.py | 3 + .../django/bootcamp/questions/urls.py | 18 + .../django/bootcamp/questions/views.py | 164 ++ .../django/bootcamp/search/__init__.py | 0 .../bootcamp/search/migrations/__init__.py | 0 .../django/bootcamp/search/models.py | 1 + .../bootcamp/search/static/css/search.css | 91 + .../bootcamp/search/static/js/search.js | 17 + .../search/partial_articles_results.html | 25 + .../search/partial_feed_results.html | 24 + .../search/partial_questions_results.html | 23 + .../search/partial_results_menu.html | 20 + .../search/partial_users_results.html | 20 + .../search/templates/search/results.html | 48 + .../search/templates/search/search.html | 30 + .../django/bootcamp/search/tests.py | 3 + .../django/bootcamp/search/views.py | 52 + .../django/bootcamp/settings.py | 104 + .../django/bootcamp/static/css/bootcamp.css | 76 + .../bootcamp/static/css/jquery.Jcrop.min.css | 30 + .../django/bootcamp/static/img/Jcrop.gif | Bin 0 -> 329 bytes .../django/bootcamp/static/img/favicon.png | Bin 0 -> 4256 bytes .../django/bootcamp/static/img/loading.gif | Bin 0 -> 11598 bytes .../django/bootcamp/static/img/user.png | Bin 0 -> 4693 bytes .../django/bootcamp/static/js/bootcamp.js | 13 + .../bootcamp/static/js/bootcamp.markdown.js | 87 + .../django/bootcamp/static/js/ga.js | 7 + .../bootcamp/static/js/jquery.Jcrop.min.js | 22 + .../static/js/jquery.bullseye-1.0-min.js | 10 + .../bootcamp/static/js/jquery.selection.js | 354 ++++ .../static/js/jquery.typeahead.bundle.js | 1716 +++++++++++++++++ .../django/bootcamp/templates/403.html | 8 + .../django/bootcamp/templates/404.html | 8 + .../django/bootcamp/templates/500.html | 8 + .../django/bootcamp/templates/base.html | 85 + .../bootcamp/templates/markdown_editor.html | 82 + .../django/bootcamp/templates/paginator.html | 19 + .../django/bootcamp/urls.py | 45 + .../django/bootcamp/wsgi.py | 9 + .../development-frameworks/django/manage.py | 10 + .../django/requirements.txt | 11 + .../development-frameworks/django/runtime.txt | 1 + 147 files changed, 9054 insertions(+) create mode 100644 samples/development-frameworks/django/LICENSE create mode 100644 samples/development-frameworks/django/Procfile create mode 100644 samples/development-frameworks/django/README.md create mode 100644 samples/development-frameworks/django/bootcamp/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/activities/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/activities/migrations/0001_initial.py create mode 100644 samples/development-frameworks/django/bootcamp/activities/migrations/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/activities/models.py create mode 100644 samples/development-frameworks/django/bootcamp/activities/static/css/notifications.css create mode 100644 samples/development-frameworks/django/bootcamp/activities/static/js/notifications.js create mode 100644 samples/development-frameworks/django/bootcamp/activities/templates/activities/last_notifications.html create mode 100644 samples/development-frameworks/django/bootcamp/activities/templates/activities/notifications.html create mode 100644 samples/development-frameworks/django/bootcamp/activities/tests.py create mode 100644 samples/development-frameworks/django/bootcamp/activities/views.py create mode 100644 samples/development-frameworks/django/bootcamp/articles/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/articles/forms.py create mode 100644 samples/development-frameworks/django/bootcamp/articles/migrations/0001_initial.py create mode 100644 samples/development-frameworks/django/bootcamp/articles/migrations/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/articles/models.py create mode 100644 samples/development-frameworks/django/bootcamp/articles/static/css/articles.css create mode 100644 samples/development-frameworks/django/bootcamp/articles/static/js/articles.js create mode 100644 samples/development-frameworks/django/bootcamp/articles/templates/articles/article.html create mode 100644 samples/development-frameworks/django/bootcamp/articles/templates/articles/articles.html create mode 100644 samples/development-frameworks/django/bootcamp/articles/templates/articles/drafts.html create mode 100644 samples/development-frameworks/django/bootcamp/articles/templates/articles/edit.html create mode 100644 samples/development-frameworks/django/bootcamp/articles/templates/articles/partial_article.html create mode 100644 samples/development-frameworks/django/bootcamp/articles/templates/articles/partial_article_comment.html create mode 100644 samples/development-frameworks/django/bootcamp/articles/templates/articles/partial_article_comments.html create mode 100644 samples/development-frameworks/django/bootcamp/articles/templates/articles/write.html create mode 100644 samples/development-frameworks/django/bootcamp/articles/tests/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/articles/tests/test_articles.py create mode 100644 samples/development-frameworks/django/bootcamp/articles/urls.py create mode 100644 samples/development-frameworks/django/bootcamp/articles/views.py create mode 100644 samples/development-frameworks/django/bootcamp/authentication/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/authentication/forms.py create mode 100644 samples/development-frameworks/django/bootcamp/authentication/migrations/0001_initial.py create mode 100644 samples/development-frameworks/django/bootcamp/authentication/migrations/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/authentication/models.py create mode 100644 samples/development-frameworks/django/bootcamp/authentication/static/css/signup.css create mode 100644 samples/development-frameworks/django/bootcamp/authentication/templates/authentication/signup.html create mode 100644 samples/development-frameworks/django/bootcamp/authentication/tests.py create mode 100644 samples/development-frameworks/django/bootcamp/authentication/views.py create mode 100644 samples/development-frameworks/django/bootcamp/core/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/core/forms.py create mode 100644 samples/development-frameworks/django/bootcamp/core/migrations/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/core/models.py create mode 100644 samples/development-frameworks/django/bootcamp/core/static/css/cover.css create mode 100644 samples/development-frameworks/django/bootcamp/core/static/css/network.css create mode 100644 samples/development-frameworks/django/bootcamp/core/static/css/profile.css create mode 100644 samples/development-frameworks/django/bootcamp/core/static/js/picture.js create mode 100644 samples/development-frameworks/django/bootcamp/core/templates/core/cover.html create mode 100644 samples/development-frameworks/django/bootcamp/core/templates/core/network.html create mode 100644 samples/development-frameworks/django/bootcamp/core/templates/core/partial_settings_menu.html create mode 100644 samples/development-frameworks/django/bootcamp/core/templates/core/password.html create mode 100644 samples/development-frameworks/django/bootcamp/core/templates/core/picture.html create mode 100644 samples/development-frameworks/django/bootcamp/core/templates/core/profile.html create mode 100644 samples/development-frameworks/django/bootcamp/core/templates/core/settings.html create mode 100644 samples/development-frameworks/django/bootcamp/core/tests.py create mode 100644 samples/development-frameworks/django/bootcamp/core/views.py create mode 100644 samples/development-frameworks/django/bootcamp/decorators.py create mode 100644 samples/development-frameworks/django/bootcamp/feeds/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/feeds/migrations/0001_initial.py create mode 100644 samples/development-frameworks/django/bootcamp/feeds/migrations/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/feeds/models.py create mode 100644 samples/development-frameworks/django/bootcamp/feeds/static/css/feeds.css create mode 100644 samples/development-frameworks/django/bootcamp/feeds/static/js/feeds.js create mode 100644 samples/development-frameworks/django/bootcamp/feeds/templates/feeds/feed.html create mode 100644 samples/development-frameworks/django/bootcamp/feeds/templates/feeds/feeds.html create mode 100644 samples/development-frameworks/django/bootcamp/feeds/templates/feeds/partial_feed.html create mode 100644 samples/development-frameworks/django/bootcamp/feeds/templates/feeds/partial_feed_comments.html create mode 100644 samples/development-frameworks/django/bootcamp/feeds/tests.py create mode 100644 samples/development-frameworks/django/bootcamp/feeds/urls.py create mode 100644 samples/development-frameworks/django/bootcamp/feeds/views.py create mode 100644 samples/development-frameworks/django/bootcamp/locale/es/LC_MESSAGES/django.po create mode 100644 samples/development-frameworks/django/bootcamp/locale/jp/LC_MESSAGES/django.po create mode 100644 samples/development-frameworks/django/bootcamp/locale/pt_BR/LC_MESSAGES/django.po create mode 100644 samples/development-frameworks/django/bootcamp/locale/zh_CN/LC_MESSAGES/django.po create mode 100644 samples/development-frameworks/django/bootcamp/messenger/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/messenger/migrations/0001_initial.py create mode 100644 samples/development-frameworks/django/bootcamp/messenger/migrations/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/messenger/models.py create mode 100644 samples/development-frameworks/django/bootcamp/messenger/static/css/messages.css create mode 100644 samples/development-frameworks/django/bootcamp/messenger/static/js/check_messages.js create mode 100644 samples/development-frameworks/django/bootcamp/messenger/static/js/messages.js create mode 100644 samples/development-frameworks/django/bootcamp/messenger/static/js/messages.typehead.js create mode 100644 samples/development-frameworks/django/bootcamp/messenger/templates/messenger/base_messages.html create mode 100644 samples/development-frameworks/django/bootcamp/messenger/templates/messenger/inbox.html create mode 100644 samples/development-frameworks/django/bootcamp/messenger/templates/messenger/includes/partial_conversations.html create mode 100644 samples/development-frameworks/django/bootcamp/messenger/templates/messenger/includes/partial_message.html create mode 100644 samples/development-frameworks/django/bootcamp/messenger/templates/messenger/new.html create mode 100644 samples/development-frameworks/django/bootcamp/messenger/tests.py create mode 100644 samples/development-frameworks/django/bootcamp/messenger/urls.py create mode 100644 samples/development-frameworks/django/bootcamp/messenger/views.py create mode 100644 samples/development-frameworks/django/bootcamp/questions/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/questions/forms.py create mode 100644 samples/development-frameworks/django/bootcamp/questions/migrations/0001_initial.py create mode 100644 samples/development-frameworks/django/bootcamp/questions/migrations/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/questions/models.py create mode 100644 samples/development-frameworks/django/bootcamp/questions/static/css/questions.css create mode 100644 samples/development-frameworks/django/bootcamp/questions/static/js/questions.js create mode 100644 samples/development-frameworks/django/bootcamp/questions/templates/questions/ask.html create mode 100644 samples/development-frameworks/django/bootcamp/questions/templates/questions/partial_answer.html create mode 100644 samples/development-frameworks/django/bootcamp/questions/templates/questions/partial_question.html create mode 100644 samples/development-frameworks/django/bootcamp/questions/templates/questions/question.html create mode 100644 samples/development-frameworks/django/bootcamp/questions/templates/questions/questions.html create mode 100644 samples/development-frameworks/django/bootcamp/questions/tests.py create mode 100644 samples/development-frameworks/django/bootcamp/questions/urls.py create mode 100644 samples/development-frameworks/django/bootcamp/questions/views.py create mode 100644 samples/development-frameworks/django/bootcamp/search/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/search/migrations/__init__.py create mode 100644 samples/development-frameworks/django/bootcamp/search/models.py create mode 100644 samples/development-frameworks/django/bootcamp/search/static/css/search.css create mode 100644 samples/development-frameworks/django/bootcamp/search/static/js/search.js create mode 100644 samples/development-frameworks/django/bootcamp/search/templates/search/partial_articles_results.html create mode 100644 samples/development-frameworks/django/bootcamp/search/templates/search/partial_feed_results.html create mode 100644 samples/development-frameworks/django/bootcamp/search/templates/search/partial_questions_results.html create mode 100644 samples/development-frameworks/django/bootcamp/search/templates/search/partial_results_menu.html create mode 100644 samples/development-frameworks/django/bootcamp/search/templates/search/partial_users_results.html create mode 100644 samples/development-frameworks/django/bootcamp/search/templates/search/results.html create mode 100644 samples/development-frameworks/django/bootcamp/search/templates/search/search.html create mode 100644 samples/development-frameworks/django/bootcamp/search/tests.py create mode 100644 samples/development-frameworks/django/bootcamp/search/views.py create mode 100644 samples/development-frameworks/django/bootcamp/settings.py create mode 100644 samples/development-frameworks/django/bootcamp/static/css/bootcamp.css create mode 100644 samples/development-frameworks/django/bootcamp/static/css/jquery.Jcrop.min.css create mode 100644 samples/development-frameworks/django/bootcamp/static/img/Jcrop.gif create mode 100644 samples/development-frameworks/django/bootcamp/static/img/favicon.png create mode 100644 samples/development-frameworks/django/bootcamp/static/img/loading.gif create mode 100644 samples/development-frameworks/django/bootcamp/static/img/user.png create mode 100644 samples/development-frameworks/django/bootcamp/static/js/bootcamp.js create mode 100644 samples/development-frameworks/django/bootcamp/static/js/bootcamp.markdown.js create mode 100644 samples/development-frameworks/django/bootcamp/static/js/ga.js create mode 100644 samples/development-frameworks/django/bootcamp/static/js/jquery.Jcrop.min.js create mode 100644 samples/development-frameworks/django/bootcamp/static/js/jquery.bullseye-1.0-min.js create mode 100644 samples/development-frameworks/django/bootcamp/static/js/jquery.selection.js create mode 100644 samples/development-frameworks/django/bootcamp/static/js/jquery.typeahead.bundle.js create mode 100644 samples/development-frameworks/django/bootcamp/templates/403.html create mode 100644 samples/development-frameworks/django/bootcamp/templates/404.html create mode 100644 samples/development-frameworks/django/bootcamp/templates/500.html create mode 100644 samples/development-frameworks/django/bootcamp/templates/base.html create mode 100644 samples/development-frameworks/django/bootcamp/templates/markdown_editor.html create mode 100644 samples/development-frameworks/django/bootcamp/templates/paginator.html create mode 100644 samples/development-frameworks/django/bootcamp/urls.py create mode 100644 samples/development-frameworks/django/bootcamp/wsgi.py create mode 100644 samples/development-frameworks/django/manage.py create mode 100644 samples/development-frameworks/django/requirements.txt create mode 100644 samples/development-frameworks/django/runtime.txt diff --git a/samples/development-frameworks/django/LICENSE b/samples/development-frameworks/django/LICENSE new file mode 100644 index 00000000..fc4e74e5 --- /dev/null +++ b/samples/development-frameworks/django/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Vitor Freitas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/samples/development-frameworks/django/Procfile b/samples/development-frameworks/django/Procfile new file mode 100644 index 00000000..d4f4007b --- /dev/null +++ b/samples/development-frameworks/django/Procfile @@ -0,0 +1 @@ +web: gunicorn bootcamp.wsgi --log-file - \ No newline at end of file diff --git a/samples/development-frameworks/django/README.md b/samples/development-frameworks/django/README.md new file mode 100644 index 00000000..1c4f3a02 --- /dev/null +++ b/samples/development-frameworks/django/README.md @@ -0,0 +1,87 @@ +# Bootcamp + +[![Build Status](https://travis-ci.org/vitorfs/bootcamp.svg?branch=master)](https://travis-ci.org/vitorfs/bootcamp) + +Bootcamp is an open source **enterprise social network** built with [Python][0] using the [Django Web Framework][1]. + +The project has three basic apps: + +* Feed (A Twitter-like microblog) +* Articles (A collaborative blog) +* Question & Answers (A Stack Overflow-like platform) + +## Feed App + +The Feed app has infinite scrolling, activity notifications, live updates for likes and comments, and comment tracking. + + +## Articles App + +The Articles app is a basic blog, with articles pagination, tag filtering and draft management. + + +## Question & Answers App + +The Q&A app works just like Stack Overflow. You can mark a question as favorite, vote up or vote down answers, accept an answer and so on. + + +## Technology Stack + +- Python 2.7 +- Django 1.9.4 +- Twitter Bootstrap 3 +- jQuery 2 + + +## Installation + +### 1 Install Python 2.7 and Django Framework 1.9 + +**Python 2.7.x** +https://www.python.org/downloads/ + + + +### 2 Install dependencies +On the project root there is a requirements.pip file. Make sure you install all the required dependencies before running Bootcamp + + pip install -U -r requirements.txt + +**Note:** If you are having problems with Pillow installation please take a look on a detailed installation guide at: http://pillow.readthedocs.org/en/latest/installation.html + + +### 3 Syncdb + +Edit your settings.py file with your database information + + DATABASES = { + 'default': { + 'ENGINE': 'sql_server.pyodbc', + 'NAME': 'django', + 'USER': 'yourusername', + 'PASSWORD': 'yourpassword', + 'HOST': 'yourserver', + 'PORT': '1433', + + 'OPTIONS': { + 'driver': 'ODBC Driver 13 for SQL Server', + }, + }, + } + +Then run the database migration + + python manage.py migrate + +### 4 Run + + python manage.py runserver + + +## Demo + +Try Bootcamp now at [http://trybootcamp.vitorfs.com][2]. + +[0]: https://www.python.org/ +[1]: https://www.djangoproject.com/ +[2]: http://trybootcamp.vitorfs.com/ diff --git a/samples/development-frameworks/django/bootcamp/__init__.py b/samples/development-frameworks/django/bootcamp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/activities/__init__.py b/samples/development-frameworks/django/bootcamp/activities/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/activities/migrations/0001_initial.py b/samples/development-frameworks/django/bootcamp/activities/migrations/0001_initial.py new file mode 100644 index 00000000..c8c9a7bf --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/activities/migrations/0001_initial.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-03-18 13:32 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('feeds', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('questions', '0001_initial'), + ('articles', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Activity', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('activity_type', models.CharField(choices=[(b'F', b'Favorite'), (b'L', b'Like'), (b'U', b'Up Vote'), (b'D', b'Down Vote')], max_length=1)), + ('date', models.DateTimeField(auto_now_add=True)), + ('feed', models.IntegerField(blank=True, null=True)), + ('question', models.IntegerField(blank=True, null=True)), + ('answer', models.IntegerField(blank=True, null=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Activity', + 'verbose_name_plural': 'Activities', + }, + ), + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateTimeField(auto_now_add=True)), + ('notification_type', models.CharField(choices=[(b'L', b'Liked'), (b'C', b'Commented'), (b'F', b'Favorited'), (b'A', b'Answered'), (b'W', b'Accepted Answer'), (b'E', b'Edited Article'), (b'S', b'Also Commented')], max_length=1)), + ('is_read', models.BooleanField(default=False)), + ('answer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='questions.Answer')), + ('article', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='articles.Article')), + ('feed', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='feeds.Feed')), + ('from_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), + ('question', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='questions.Question')), + ('to_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('-date',), + 'verbose_name': 'Notification', + 'verbose_name_plural': 'Notifications', + }, + ), + ] diff --git a/samples/development-frameworks/django/bootcamp/activities/migrations/__init__.py b/samples/development-frameworks/django/bootcamp/activities/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/activities/models.py b/samples/development-frameworks/django/bootcamp/activities/models.py new file mode 100644 index 00000000..16c417c6 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/activities/models.py @@ -0,0 +1,142 @@ +from django.db import models +from django.contrib.auth.models import User +from django.utils.html import escape + + +class Activity(models.Model): + FAVORITE = 'F' + LIKE = 'L' + UP_VOTE = 'U' + DOWN_VOTE = 'D' + ACTIVITY_TYPES = ( + (FAVORITE, 'Favorite'), + (LIKE, 'Like'), + (UP_VOTE, 'Up Vote'), + (DOWN_VOTE, 'Down Vote'), + ) + + user = models.ForeignKey(User) + activity_type = models.CharField(max_length=1, choices=ACTIVITY_TYPES) + date = models.DateTimeField(auto_now_add=True) + feed = models.IntegerField(null=True, blank=True) + question = models.IntegerField(null=True, blank=True) + answer = models.IntegerField(null=True, blank=True) + + class Meta: + verbose_name = 'Activity' + verbose_name_plural = 'Activities' + + def __unicode__(self): + return self.activity_type + +# def save(self, *args, **kwargs): +# super(Activity, self).save(*args, **kwargs) +# if self.activity_type == Activity.FAVORITE: +# Question = models.get_model('questions', 'Question') +# question = Question.objects.get(pk=self.question) +# user = question.user +# user.profile.reputation = user.profile.reputation + 5 +# user.save() + + +class Notification(models.Model): + LIKED = 'L' + COMMENTED = 'C' + FAVORITED = 'F' + ANSWERED = 'A' + ACCEPTED_ANSWER = 'W' + EDITED_ARTICLE = 'E' + ALSO_COMMENTED = 'S' + NOTIFICATION_TYPES = ( + (LIKED, 'Liked'), + (COMMENTED, 'Commented'), + (FAVORITED, 'Favorited'), + (ANSWERED, 'Answered'), + (ACCEPTED_ANSWER, 'Accepted Answer'), + (EDITED_ARTICLE, 'Edited Article'), + (ALSO_COMMENTED, 'Also Commented'), + ) + + _LIKED_TEMPLATE = u'{1} liked your post: {3}' + _COMMENTED_TEMPLATE = u'{1} commented on your post: {3}' + _FAVORITED_TEMPLATE = u'{1} favorited your question: {3}' + _ANSWERED_TEMPLATE = u'{1} answered your question: {3}' + _ACCEPTED_ANSWER_TEMPLATE = u'{1} accepted your answer: {3}' + _EDITED_ARTICLE_TEMPLATE = u'{1} edited your article: {3}' + _ALSO_COMMENTED_TEMPLATE = u'{1} also commentend on the post: {3}' + + from_user = models.ForeignKey(User, related_name='+') + to_user = models.ForeignKey(User, related_name='+') + date = models.DateTimeField(auto_now_add=True) + feed = models.ForeignKey('feeds.Feed', null=True, blank=True) + question = models.ForeignKey('questions.Question', null=True, blank=True) + answer = models.ForeignKey('questions.Answer', null=True, blank=True) + article = models.ForeignKey('articles.Article', null=True, blank=True) + notification_type = models.CharField(max_length=1, choices=NOTIFICATION_TYPES) + is_read = models.BooleanField(default=False) + + class Meta: + verbose_name = 'Notification' + verbose_name_plural = 'Notifications' + ordering = ('-date',) + + def __unicode__(self): + if self.notification_type == self.LIKED: + return self._LIKED_TEMPLATE.format( + escape(self.from_user.username), + escape(self.from_user.profile.get_screen_name()), + self.feed.pk, + escape(self.get_summary(self.feed.post)) + ) + elif self.notification_type == self.COMMENTED: + return self._COMMENTED_TEMPLATE.format( + escape(self.from_user.username), + escape(self.from_user.profile.get_screen_name()), + self.feed.pk, + escape(self.get_summary(self.feed.post)) + ) + elif self.notification_type == self.FAVORITED: + return self._FAVORITED_TEMPLATE.format( + escape(self.from_user.username), + escape(self.from_user.profile.get_screen_name()), + self.question.pk, + escape(self.get_summary(self.question.title)) + ) + elif self.notification_type == self.ANSWERED: + return self._ANSWERED_TEMPLATE.format( + escape(self.from_user.username), + escape(self.from_user.profile.get_screen_name()), + self.question.pk, + escape(self.get_summary(self.question.title)) + ) + elif self.notification_type == self.ACCEPTED_ANSWER: + return self._ACCEPTED_ANSWER_TEMPLATE.format( + escape(self.from_user.username), + escape(self.from_user.profile.get_screen_name()), + self.answer.question.pk, + escape(self.get_summary(self.answer.description)) + ) + elif self.notification_type == self.EDITED_ARTICLE: + return self._EDITED_ARTICLE_TEMPLATE.format( + escape(self.from_user.username), + escape(self.from_user.profile.get_screen_name()), + self.article.slug, + escape(self.get_summary(self.article.title)) + ) + elif self.notification_type == self.ALSO_COMMENTED: + return self._ALSO_COMMENTED_TEMPLATE.format( + escape(self.from_user.username), + escape(self.from_user.profile.get_screen_name()), + self.feed.pk, + escape(self.get_summary(self.feed.post)) + ) + else: + return 'Ooops! Something went wrong.' + + def get_summary(self, value): + summary_size = 50 + if len(value) > summary_size: + return u'{0}...'.format(value[:summary_size]) + + else: + return value diff --git a/samples/development-frameworks/django/bootcamp/activities/static/css/notifications.css b/samples/development-frameworks/django/bootcamp/activities/static/css/notifications.css new file mode 100644 index 00000000..160df29d --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/activities/static/css/notifications.css @@ -0,0 +1,33 @@ +ul.all-notifications { + padding: 0; + margin-top: 1em; +} + +ul.all-notifications li { + list-style: none; + border-bottom: 1px solid #eeeeee; + padding: .8em 0; +} + +ul.all-notifications li:last-child { + border-bottom: none; +} + +ul.all-notifications li small { + color: #cccccc; + font-size: .8em; +} + +ul.all-notifications li p { + margin: 0; +} + +ul.all-notifications li div { + margin-left: 40px; + padding-left: 1em; +} + +ul.all-notifications .user-picture { + width: 40px; + float: left; +} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/activities/static/js/notifications.js b/samples/development-frameworks/django/bootcamp/activities/static/js/notifications.js new file mode 100644 index 00000000..ff839b40 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/activities/static/js/notifications.js @@ -0,0 +1,42 @@ +$(function () { + $('#notifications').popover({html: true, content: 'Loading...', trigger: 'manual'}); + + $("#notifications").click(function () { + if ($(".popover").is(":visible")) { + $("#notifications").popover('hide'); + } + else { + $("#notifications").popover('show'); + $.ajax({ + url: '/notifications/last/', + beforeSend: function () { + $(".popover-content").html("
"); + $("#notifications").removeClass("new-notifications"); + }, + success: function (data) { + $(".popover-content").html(data); + } + }); + } + return false; + }); + + function check_notifications() { + $.ajax({ + url: '/notifications/check/', + cache: false, + success: function (data) { + if (data != "0") { + $("#notifications").addClass("new-notifications"); + } + else { + $("#notifications").removeClass("new-notifications"); + } + }, + complete: function () { + window.setTimeout(check_notifications, 30000); + } + }); + }; + check_notifications(); +}); \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/activities/templates/activities/last_notifications.html b/samples/development-frameworks/django/bootcamp/activities/templates/activities/last_notifications.html new file mode 100644 index 00000000..fd7f6e0e --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/activities/templates/activities/last_notifications.html @@ -0,0 +1,15 @@ +{% load i18n %} +{% load humanize %} + + diff --git a/samples/development-frameworks/django/bootcamp/activities/templates/activities/notifications.html b/samples/development-frameworks/django/bootcamp/activities/templates/activities/notifications.html new file mode 100644 index 00000000..9b7eb15d --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/activities/templates/activities/notifications.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% load humanize %} + +{% block title %} Notifications {% endblock %} + +{% block head %} + +{% endblock head %} + +{% block main %} + + +{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/activities/tests.py b/samples/development-frameworks/django/bootcamp/activities/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/activities/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/samples/development-frameworks/django/bootcamp/activities/views.py b/samples/development-frameworks/django/bootcamp/activities/views.py new file mode 100644 index 00000000..6b4312f7 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/activities/views.py @@ -0,0 +1,42 @@ +from django.shortcuts import render +from bootcamp.activities.models import Notification +from django.http import HttpResponse +from django.contrib.auth.decorators import login_required +from bootcamp.decorators import ajax_required + + +@login_required +def notifications(request): + user = request.user + notifications = Notification.objects.filter(to_user=user) + unread = Notification.objects.filter(to_user=user, is_read=False) + for notification in unread: + notification.is_read = True + notification.save() + + return render(request, 'activities/notifications.html', + {'notifications': notifications}) + + +@login_required +@ajax_required +def last_notifications(request): + user = request.user + notifications = Notification.objects.filter(to_user=user, + is_read=False)[:5] + for notification in notifications: + notification.is_read = True + notification.save() + + return render(request, + 'activities/last_notifications.html', + {'notifications': notifications}) + + +@login_required +@ajax_required +def check_notifications(request): + user = request.user + notifications = Notification.objects.filter(to_user=user, + is_read=False)[:5] + return HttpResponse(len(notifications)) diff --git a/samples/development-frameworks/django/bootcamp/articles/__init__.py b/samples/development-frameworks/django/bootcamp/articles/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/articles/forms.py b/samples/development-frameworks/django/bootcamp/articles/forms.py new file mode 100644 index 00000000..7ee9346b --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/forms.py @@ -0,0 +1,20 @@ +from django import forms +from bootcamp.articles.models import Article + + +class ArticleForm(forms.ModelForm): + status = forms.CharField(widget=forms.HiddenInput()) + title = forms.CharField( + widget=forms.TextInput(attrs={'class': 'form-control'}), + max_length=255) + content = forms.CharField( + widget=forms.Textarea(attrs={'class': 'form-control'}), + max_length=4000) + tags = forms.CharField( + widget=forms.TextInput(attrs={'class': 'form-control'}), + max_length=255, required=False, + help_text='Use spaces to separate the tags, such as "java jsf primefaces"') + + class Meta: + model = Article + fields = ['title', 'content', 'tags', 'status'] diff --git a/samples/development-frameworks/django/bootcamp/articles/migrations/0001_initial.py b/samples/development-frameworks/django/bootcamp/articles/migrations/0001_initial.py new file mode 100644 index 00000000..3edb75d9 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/migrations/0001_initial.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-03-18 13:32 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Article', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('slug', models.SlugField(blank=True, max_length=255, null=True)), + ('content', models.TextField(max_length=4000)), + ('status', models.CharField(choices=[(b'D', b'Draft'), (b'P', b'Published')], default=b'D', max_length=1)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('update_date', models.DateTimeField(blank=True, null=True)), + ('create_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('update_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('-create_date',), + 'verbose_name': 'Article', + 'verbose_name_plural': 'Articles', + }, + ), + migrations.CreateModel( + name='ArticleComment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('comment', models.CharField(max_length=500)), + ('date', models.DateTimeField(auto_now_add=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='articles.Article')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('date',), + 'verbose_name': 'Article Comment', + 'verbose_name_plural': 'Article Comments', + }, + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tag', models.CharField(max_length=50)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='articles.Article')), + ], + options={ + 'verbose_name': 'Tag', + 'verbose_name_plural': 'Tags', + }, + ), + migrations.AlterUniqueTogether( + name='tag', + unique_together=set([('tag', 'article')]), + ), + migrations.AlterIndexTogether( + name='tag', + index_together=set([('tag', 'article')]), + ), + ] diff --git a/samples/development-frameworks/django/bootcamp/articles/migrations/__init__.py b/samples/development-frameworks/django/bootcamp/articles/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/articles/models.py b/samples/development-frameworks/django/bootcamp/articles/models.py new file mode 100644 index 00000000..51234f14 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/models.py @@ -0,0 +1,116 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.models import User +from datetime import datetime +from django.template.defaultfilters import slugify +import markdown + + +class Article(models.Model): + DRAFT = 'D' + PUBLISHED = 'P' + STATUS = ( + (DRAFT, 'Draft'), + (PUBLISHED, 'Published'), + ) + + title = models.CharField(max_length=255) + slug = models.SlugField(max_length=255, null=True, blank=True) + content = models.TextField(max_length=4000) + status = models.CharField(max_length=1, choices=STATUS, default=DRAFT) + create_user = models.ForeignKey(User) + create_date = models.DateTimeField(auto_now_add=True) + update_date = models.DateTimeField(blank=True, null=True) + update_user = models.ForeignKey(User, null=True, blank=True, + related_name="+") + + class Meta: + verbose_name = _("Article") + verbose_name_plural = _("Articles") + ordering = ("-create_date",) + + def __unicode__(self): + return self.title + + def save(self, *args, **kwargs): + if not self.pk: + super(Article, self).save(*args, **kwargs) + else: + self.update_date = datetime.now() + if not self.slug: + slug_str = "%s %s" % (self.pk, self.title.lower()) + self.slug = slugify(slug_str) + super(Article, self).save(*args, **kwargs) + + def get_content_as_markdown(self): + return markdown.markdown(self.content, safe_mode='escape') + + @staticmethod + def get_published(): + articles = Article.objects.filter(status=Article.PUBLISHED) + return articles + + def create_tags(self, tags): + tags = tags.strip() + tag_list = tags.split(' ') + for tag in tag_list: + if tag: + t, created = Tag.objects.get_or_create(tag=tag.lower(), + article=self) + + def get_tags(self): + return Tag.objects.filter(article=self) + + def get_summary(self): + if len(self.content) > 255: + return u'{0}...'.format(self.content[:255]) + else: + return self.content + + def get_summary_as_markdown(self): + return markdown.markdown(self.get_summary(), safe_mode='escape') + + def get_comments(self): + return ArticleComment.objects.filter(article=self) + + +class Tag(models.Model): + tag = models.CharField(max_length=50) + article = models.ForeignKey(Article) + + class Meta: + verbose_name = _('Tag') + verbose_name_plural = _('Tags') + unique_together = (('tag', 'article'),) + index_together = [['tag', 'article'], ] + + def __unicode__(self): + return self.tag + + @staticmethod + def get_popular_tags(): + tags = Tag.objects.all() + count = {} + for tag in tags: + if tag.article.status == Article.PUBLISHED: + if tag.tag in count: + count[tag.tag] = count[tag.tag] + 1 + else: + count[tag.tag] = 1 + sorted_count = sorted(count.items(), key=lambda t: t[1], reverse=True) + return sorted_count[:20] + + +class ArticleComment(models.Model): + article = models.ForeignKey(Article) + comment = models.CharField(max_length=500) + date = models.DateTimeField(auto_now_add=True) + user = models.ForeignKey(User) + + class Meta: + verbose_name = _("Article Comment") + verbose_name_plural = _("Article Comments") + ordering = ("date",) + + def __unicode__(self): + return u'{0} - {1}'.format(self.user.username, self.article.title) diff --git a/samples/development-frameworks/django/bootcamp/articles/static/css/articles.css b/samples/development-frameworks/django/bootcamp/articles/static/css/articles.css new file mode 100644 index 00000000..ebdec821 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/static/css/articles.css @@ -0,0 +1,103 @@ +.articles article { + padding-bottom: 1.4em; + border-bottom: 1px solid #eeeeee; + margin-top: 2.4em; +} + +.articles article p { + font-size: 1.2em; +} + +.articles article:last-child { + border-bottom: none; +} + +.info { + margin-bottom: .5em; + color: #a0a0a0; +} + +.info a { + color: #a0a0a0; +} + +.info > span { + margin-right: 1em; +} + +.info .user img { + width: 20px; + border-radius: 3px; +} + +article .tags { + font-size: 1em; +} + +.popular-tags h4 { + margin-top: 2em; +} + +.popular-tags .label { + margin-top: .4em; + font-size: .8em; + line-height: 2; +} + +.popular-tags a:hover, .tags a:hover { + text-decoration: none; +} + +.user-portrait img { + border-radius: 4px; + width: 34px; +} + +.user-portrait { + width: 40px; + float: left; +} + +.comment-input { + float: left; + width: -moz-calc(100% - 40px); + width: -webkit-calc(100% - 40px); + width: calc(100% - 40px); +} + +.post-comment { + margin-bottom: 1em; +} + +.comment-portrait { + width: 40px; + border-radius: 5px; + float: left; +} + +.comment-text { + margin-left: 50px; +} + +.comment-text h5 { + padding-top: .1em; + margin: .2em 0; +} + +.comment-text h5 small { + margin-left: .6em; +} + +.comment-text p { + margin: 0; + font-size: .9em; +} + +.comment { + border-bottom: 1px solid #e3e3e3; + padding: .8em 0; +} + +.comment:last-child { + border-bottom: none; +} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/articles/static/js/articles.js b/samples/development-frameworks/django/bootcamp/articles/static/js/articles.js new file mode 100644 index 00000000..954f3d8a --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/static/js/articles.js @@ -0,0 +1,56 @@ +$(function () { + $(".publish").click(function () { + $("input[name='status']").val("P"); + $("form").submit(); + }); + + $(".draft").click(function () { + $("input[name='status']").val("D"); + $("form").submit(); + }); + + $(".preview").click(function () { + $.ajax({ + url: '/articles/preview/', + data: $("form").serialize(), + cache: false, + type: 'post', + beforeSend: function () { + $("#preview .modal-body").html("
"); + }, + success: function (data) { + $("#preview .modal-body").html(data); + } + }); + }); + + $("#comment").focus(function () { + $(this).attr("rows", "3"); + $("#comment-helper").fadeIn(); + }); + + $("#comment").blur(function () { + $(this).attr("rows", "1"); + $("#comment-helper").fadeOut(); + }); + + $("#comment").keydown(function (evt) { + var keyCode = evt.which?evt.which:evt.keyCode; + if (evt.ctrlKey && (keyCode == 10 || keyCode == 13)) { + $.ajax({ + url: '/articles/comment/', + data: $("#comment-form").serialize(), + cache: false, + type: 'post', + success: function (data) { + $("#comment-list").html(data); + var comment_count = $("#comment-list .comment").length; + $(".comment-count").text(comment_count); + $("#comment").val(""); + $("#comment").blur(); + } + }); + } + }); + +}); \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/articles/templates/articles/article.html b/samples/development-frameworks/django/bootcamp/articles/templates/articles/article.html new file mode 100644 index 00000000..d7427196 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/templates/articles/article.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% block title %}{{ article.title }}{% endblock %} + +{% block head %} + + +{% endblock head %} + +{% block main %} + + {% include 'articles/partial_article.html' with article=article %} + {% include 'articles/partial_article_comments.html' with article=article %} +{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/articles/templates/articles/articles.html b/samples/development-frameworks/django/bootcamp/articles/templates/articles/articles.html new file mode 100644 index 00000000..83f31170 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/templates/articles/articles.html @@ -0,0 +1,41 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% block title %} {% trans 'Articles' %} {% endblock %} + +{% block head %} + +{% endblock head %} + +{% block main %} + +
+
+
+ {% for article in articles %} + {% include 'articles/partial_article.html' with article=article %} + {% empty %} +

{% trans 'There is no published article yet' %}. {% trans 'Be the first one to publish' %}!

+ {% endfor %} +
+
+ +
+
+
+ {% include 'paginator.html' with paginator=articles %} +
+
+{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/articles/templates/articles/drafts.html b/samples/development-frameworks/django/bootcamp/articles/templates/articles/drafts.html new file mode 100644 index 00000000..b54b0fc5 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/templates/articles/drafts.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% block head %} + +{% endblock head %} + +{% block main %} + + + + + + + + + + + {% for article in drafts %} + + + + + + {% empty %} + + + + {% endfor %} + +
{% trans 'Title' %}{% trans 'Content' %}{% trans 'Tags' %}
{{ article.title }}{{ article.get_summary_as_markdown|safe }} + {% for tag in article.get_tags %} + {{ tag }} + {% endfor %} +
+ {% trans 'No draft to display' %} +
+{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/articles/templates/articles/edit.html b/samples/development-frameworks/django/bootcamp/articles/templates/articles/edit.html new file mode 100644 index 00000000..1d992c05 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/templates/articles/edit.html @@ -0,0 +1,39 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% block head %} + +{% endblock head %} + +{% block main %} + +
+ {% csrf_token %} + {{ form.status }} + {% for field in form.visible_fields %} +
+ + {% if field.label == 'Content' %} + {% include 'markdown_editor.html' with textarea='id_content' %} + {% endif %} + {{ field }} + {% if field.help_text %} + {{ field.help_text }} + {% endif %} + {% for error in field.errors %} + + {% endfor %} +
+ {% endfor %} +
+ + + {% trans 'Cancel' %} +
+
+{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/articles/templates/articles/partial_article.html b/samples/development-frameworks/django/bootcamp/articles/templates/articles/partial_article.html new file mode 100644 index 00000000..4c7c2af6 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/templates/articles/partial_article.html @@ -0,0 +1,27 @@ +
+

{{ article.title }}

+
+ + + {{ article.create_date }} + + + + {{ article.create_user.profile.get_screen_name }} + + + + {{ article.get_comments.count }} Comments + +
+
+ {{ article.get_content_as_markdown|safe }} +
+ {% if article.get_tags %} +
+ {% for tag in article.get_tags %} + {{ tag }} + {% endfor %} +
+ {% endif %} +
\ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/articles/templates/articles/partial_article_comment.html b/samples/development-frameworks/django/bootcamp/articles/templates/articles/partial_article_comment.html new file mode 100644 index 00000000..fd512296 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/templates/articles/partial_article_comment.html @@ -0,0 +1,12 @@ +{% load humanize %} + +
+ +
+
+ {{ comment.user.profile.get_screen_name }} + {{ comment.date|naturaltime }} +
+

{{ comment.comment }}

+
+
\ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/articles/templates/articles/partial_article_comments.html b/samples/development-frameworks/django/bootcamp/articles/templates/articles/partial_article_comments.html new file mode 100644 index 00000000..fcf77c25 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/templates/articles/partial_article_comments.html @@ -0,0 +1,23 @@ +{% load i18n %} +
+ +

{{ article.get_comments.count }} {% trans 'Comments' %}

+
+
+ {% csrf_token %} + +
+ +
+
+ +
+
+
+
+ {% for comment in article.get_comments %} + {% include 'articles/partial_article_comment.html' with comment=comment %} + {% empty %} +
{% trans 'Be the first one to comment!' %}
+ {% endfor %} +
\ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/articles/templates/articles/write.html b/samples/development-frameworks/django/bootcamp/articles/templates/articles/write.html new file mode 100644 index 00000000..fc80219f --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/templates/articles/write.html @@ -0,0 +1,55 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% block head %} + +{% endblock head %} + +{% block main %} + +
+ {% csrf_token %} + {{ form.status }} + {% for field in form.visible_fields %} +
+ + {% if field.label == 'Content' %} + {% include 'markdown_editor.html' with textarea='id_content' %} + {% endif %} + {{ field }} + {% if field.help_text %} + {{ field.help_text }} + {% endif %} + {% for error in field.errors %} + + {% endfor %} +
+ {% endfor %} +
+ + + + {% trans 'Cancel' %} +
+
+ + + +{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/articles/tests/__init__.py b/samples/development-frameworks/django/bootcamp/articles/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/articles/tests/test_articles.py b/samples/development-frameworks/django/bootcamp/articles/tests/test_articles.py new file mode 100644 index 00000000..de52e152 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/tests/test_articles.py @@ -0,0 +1,39 @@ +from django.test import TestCase, Client, RequestFactory +from django.core.urlresolvers import reverse +from bootcamp.articles.models import Article +from django.contrib.auth.models import User + + +class ArticleTest(TestCase): + def setUp(self): + self.client = Client() + self.factory = RequestFactory() + + def test_validate_article_edition(self): + c = Client() + user1 = User.objects.create_user(username="teste1234", + email="reallynice@gmail.com", + password="supersecret123") + user2 = User.objects.create_user(username="teste12345", + email="reallynice2@gmail.com", + password="supersecret123") + + article = Article() + article.title = "nicetitle" + article.content = "nicecontent" + article.create_user = user2 + article.create_user.id = user2.id + article.save() + self.client.login(username="teste1234", password="supersecret123") + response = self.client.get(reverse('edit_article', kwargs={'id': '1'})) + self.assertEqual(response.status_code, 302) + response = self.client.post(reverse('edit_article', + kwargs={'id': '1'})) + self.assertEqual(response.status_code, 302) + self.client.login(username="teste12345", password="supersecret123") + response = self.client.get(reverse('edit_article', kwargs={'id': '1'}), + user=user2) + self.assertEqual(response.status_code, 200) + response = self.client.post(reverse('edit_article', + kwargs={'id': '1'})) + self.assertEqual(response.status_code, 200) diff --git a/samples/development-frameworks/django/bootcamp/articles/urls.py b/samples/development-frameworks/django/bootcamp/articles/urls.py new file mode 100644 index 00000000..8b22d5d8 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/urls.py @@ -0,0 +1,16 @@ +# coding: utf-8 + +from django.conf.urls import patterns, include, url + +from bootcamp.articles import views + +urlpatterns = [ + url(r'^$', views.articles, name='articles'), + url(r'^write/$', views.write, name='write'), + url(r'^preview/$', views.preview, name='preview'), + url(r'^drafts/$', views.drafts, name='drafts'), + url(r'^comment/$', views.comment, name='comment'), + url(r'^tag/(?P.+)/$', views.tag, name='tag'), + url(r'^edit/(?P\d+)/$', views.edit, name='edit_article'), + url(r'^(?P[-\w]+)/$', views.article, name='article'), +] \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/articles/views.py b/samples/development-frameworks/django/bootcamp/articles/views.py new file mode 100644 index 00000000..2e6f2850 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/articles/views.py @@ -0,0 +1,144 @@ +from django.shortcuts import render, redirect, get_object_or_404 +from django.http import HttpResponseBadRequest, HttpResponse +from bootcamp.articles.models import Article, Tag, ArticleComment +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from bootcamp.articles.forms import ArticleForm +from django.contrib.auth.decorators import login_required +from bootcamp.decorators import ajax_required +import markdown +from django.template.loader import render_to_string + + +def _articles(request, articles): + paginator = Paginator(articles, 10) + page = request.GET.get('page') + try: + articles = paginator.page(page) + except PageNotAnInteger: + articles = paginator.page(1) + except EmptyPage: + articles = paginator.page(paginator.num_pages) + popular_tags = Tag.get_popular_tags() + return render(request, 'articles/articles.html', { + 'articles': articles, + 'popular_tags': popular_tags + }) + + +@login_required +def articles(request): + all_articles = Article.get_published() + return _articles(request, all_articles) + + +@login_required +def article(request, slug): + article = get_object_or_404(Article, slug=slug, status=Article.PUBLISHED) + return render(request, 'articles/article.html', {'article': article}) + + +@login_required +def tag(request, tag_name): + tags = Tag.objects.filter(tag=tag_name) + articles = [] + for tag in tags: + if tag.article.status == Article.PUBLISHED: + articles.append(tag.article) + return _articles(request, articles) + + +@login_required +def write(request): + if request.method == 'POST': + form = ArticleForm(request.POST) + if form.is_valid(): + article = Article() + article.create_user = request.user + article.title = form.cleaned_data.get('title') + article.content = form.cleaned_data.get('content') + status = form.cleaned_data.get('status') + if status in [Article.PUBLISHED, Article.DRAFT]: + article.status = form.cleaned_data.get('status') + article.save() + tags = form.cleaned_data.get('tags') + article.create_tags(tags) + return redirect('/articles/') + else: + form = ArticleForm() + return render(request, 'articles/write.html', {'form': form}) + + +@login_required +def drafts(request): + drafts = Article.objects.filter(create_user=request.user, + status=Article.DRAFT) + return render(request, 'articles/drafts.html', {'drafts': drafts}) + + +@login_required +def edit(request, id): + tags = '' + if id: + article = get_object_or_404(Article, pk=id) + for tag in article.get_tags(): + tags = u'{0} {1}'.format(tags, tag.tag) + tags = tags.strip() + else: + article = Article(create_user=request.user) + + if article.create_user.id != request.user.id: + return redirect('home') + + if request.POST: + form = ArticleForm(request.POST, instance=article) + if form.is_valid(): + form.save() + return redirect('/articles/') + else: + form = ArticleForm(instance=article, initial={'tags': tags}) + return render(request, 'articles/edit.html', {'form': form}) + + +@login_required +@ajax_required +def preview(request): + try: + if request.method == 'POST': + content = request.POST.get('content') + html = 'Nothing to display :(' + if len(content.strip()) > 0: + html = markdown.markdown(content, safe_mode='escape') + return HttpResponse(html) + else: + return HttpResponseBadRequest() + + except Exception, e: + return HttpResponseBadRequest() + + +@login_required +@ajax_required +def comment(request): + try: + if request.method == 'POST': + article_id = request.POST.get('article') + article = Article.objects.get(pk=article_id) + comment = request.POST.get('comment') + comment = comment.strip() + if len(comment) > 0: + article_comment = ArticleComment(user=request.user, + article=article, + comment=comment) + article_comment.save() + html = u'' + for comment in article.get_comments(): + html = u'{0}{1}'.format(html, render_to_string('articles/partial_article_comment.html', + {'comment': comment})) + + return HttpResponse(html) + + else: + return HttpResponseBadRequest() + + except Exception, e: + return HttpResponseBadRequest() diff --git a/samples/development-frameworks/django/bootcamp/authentication/__init__.py b/samples/development-frameworks/django/bootcamp/authentication/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/authentication/forms.py b/samples/development-frameworks/django/bootcamp/authentication/forms.py new file mode 100644 index 00000000..ecce4a79 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/authentication/forms.py @@ -0,0 +1,92 @@ +from django import forms +from django.core.exceptions import ValidationError +from django.contrib.auth.models import User +from bootcamp.settings import ALLOWED_SIGNUP_DOMAINS + + +def SignupDomainValidator(value): + if '*' not in ALLOWED_SIGNUP_DOMAINS: + try: + domain = value[value.index("@"):] + if domain not in ALLOWED_SIGNUP_DOMAINS: + raise ValidationError(u'Invalid domain. Allowed domains on this network: {0}'.format(','.join(ALLOWED_SIGNUP_DOMAINS))) + + except Exception, e: + raise ValidationError(u'Invalid domain. Allowed domains on this network: {0}'.format(','.join(ALLOWED_SIGNUP_DOMAINS))) + + +def ForbiddenUsernamesValidator(value): + forbidden_usernames = ['admin', 'settings', 'news', 'about', 'help', + 'signin', 'signup', 'signout', 'terms', 'privacy', + 'cookie', 'new', 'login', 'logout', 'administrator', + 'join', 'account', 'username', 'root', 'blog', + 'user', 'users', 'billing', 'subscribe', 'reviews', + 'review', 'blog', 'blogs', 'edit', 'mail', 'email', + 'home', 'job', 'jobs', 'contribute', 'newsletter', + 'shop', 'profile', 'register', 'auth', + 'authentication', 'campaign', 'config', 'delete', + 'remove', 'forum', 'forums', 'download', + 'downloads', 'contact', 'blogs', 'feed', 'feeds', + 'faq', 'intranet', 'log', 'registration', 'search', + 'explore', 'rss', 'support', 'status', 'static', + 'media', 'setting', 'css', 'js', 'follow', + 'activity', 'questions', 'articles', 'network', ] + + if value.lower() in forbidden_usernames: + raise ValidationError('This is a reserved word.') + + +def InvalidUsernameValidator(value): + if '@' in value or '+' in value or '-' in value: + raise ValidationError('Enter a valid username.') + + +def UniqueEmailValidator(value): + if User.objects.filter(email__iexact=value).exists(): + raise ValidationError('User with this Email already exists.') + + +def UniqueUsernameIgnoreCaseValidator(value): + if User.objects.filter(username__iexact=value).exists(): + raise ValidationError('User with this Username already exists.') + + +class SignUpForm(forms.ModelForm): + username = forms.CharField( + widget=forms.TextInput(attrs={'class': 'form-control'}), + max_length=30, + required=True, + help_text='Usernames may contain alphanumeric, _ and . characters') + password = forms.CharField( + widget=forms.PasswordInput(attrs={'class': 'form-control'})) + confirm_password = forms.CharField( + widget=forms.PasswordInput(attrs={'class': 'form-control'}), + label="Confirm your password", + required=True) + email = forms.CharField( + widget=forms.EmailInput(attrs={'class': 'form-control'}), + required=True, + max_length=75) + + class Meta: + model = User + exclude = ['last_login', 'date_joined'] + fields = ['username', 'email', 'password', 'confirm_password', ] + + def __init__(self, *args, **kwargs): + super(SignUpForm, self).__init__(*args, **kwargs) + self.fields['username'].validators.append(ForbiddenUsernamesValidator) + self.fields['username'].validators.append(InvalidUsernameValidator) + self.fields['username'].validators.append( + UniqueUsernameIgnoreCaseValidator) + self.fields['email'].validators.append(UniqueEmailValidator) + self.fields['email'].validators.append(SignupDomainValidator) + + def clean(self): + super(SignUpForm, self).clean() + password = self.cleaned_data.get('password') + confirm_password = self.cleaned_data.get('confirm_password') + if password and password != confirm_password: + self._errors['password'] = self.error_class( + ['Passwords don\'t match']) + return self.cleaned_data diff --git a/samples/development-frameworks/django/bootcamp/authentication/migrations/0001_initial.py b/samples/development-frameworks/django/bootcamp/authentication/migrations/0001_initial.py new file mode 100644 index 00000000..24c287e1 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/authentication/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-03-18 13:32 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('location', models.CharField(blank=True, max_length=50, null=True)), + ('url', models.CharField(blank=True, max_length=50, null=True)), + ('job_title', models.CharField(blank=True, max_length=50, null=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'auth_profile', + }, + ), + ] diff --git a/samples/development-frameworks/django/bootcamp/authentication/migrations/__init__.py b/samples/development-frameworks/django/bootcamp/authentication/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/authentication/models.py b/samples/development-frameworks/django/bootcamp/authentication/models.py new file mode 100644 index 00000000..fc85b9a8 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/authentication/models.py @@ -0,0 +1,132 @@ +import urllib +import hashlib +import os.path +from django.contrib.auth.models import User +from django.db.models.signals import post_save +from django.db import models +from django.conf import settings +from bootcamp.activities.models import Notification + + +class Profile(models.Model): + user = models.OneToOneField(User) + location = models.CharField(max_length=50, null=True, blank=True) + url = models.CharField(max_length=50, null=True, blank=True) + job_title = models.CharField(max_length=50, null=True, blank=True) + # reputation = models.IntegerField(default=0) + # language = models.CharField(max_length=5, default='en') + + class Meta: + db_table = 'auth_profile' + + def get_url(self): + url = self.url + if "http://" not in self.url and "https://" not in self.url and len(self.url) > 0: + url = "http://" + str(self.url) + + return url + + def get_picture(self): + no_picture = 'http://trybootcamp.vitorfs.com/static/img/user.png' + try: + filename = settings.MEDIA_ROOT + '/profile_pictures/' + self.user.username + '.jpg' + picture_url = settings.MEDIA_URL + 'profile_pictures/' + self.user.username + '.jpg' + if os.path.isfile(filename): + return picture_url + else: + gravatar_url = u'http://www.gravatar.com/avatar/{0}?{1}'.format( + hashlib.md5(self.user.email.lower()).hexdigest(), + urllib.urlencode({'d': no_picture, 's': '256'}) + ) + return gravatar_url + + except Exception, e: + return no_picture + + def get_screen_name(self): + try: + if self.user.get_full_name(): + return self.user.get_full_name() + else: + return self.user.username + except: + return self.user.username + + def notify_liked(self, feed): + if self.user != feed.user: + Notification(notification_type=Notification.LIKED, + from_user=self.user, to_user=feed.user, + feed=feed).save() + + def unotify_liked(self, feed): + if self.user != feed.user: + Notification.objects.filter(notification_type=Notification.LIKED, + from_user=self.user, to_user=feed.user, + feed=feed).delete() + + def notify_commented(self, feed): + if self.user != feed.user: + Notification(notification_type=Notification.COMMENTED, + from_user=self.user, to_user=feed.user, + feed=feed).save() + + def notify_also_commented(self, feed): + comments = feed.get_comments() + users = [] + for comment in comments: + if comment.user != self.user and comment.user != feed.user: + users.append(comment.user.pk) + + users = list(set(users)) + for user in users: + Notification(notification_type=Notification.ALSO_COMMENTED, + from_user=self.user, + to_user=User(id=user), feed=feed).save() + + def notify_favorited(self, question): + if self.user != question.user: + Notification(notification_type=Notification.FAVORITED, + from_user=self.user, to_user=question.user, + question=question).save() + + def unotify_favorited(self, question): + if self.user != question.user: + Notification.objects.filter( + notification_type=Notification.FAVORITED, + from_user=self.user, + to_user=question.user, + question=question).delete() + + def notify_answered(self, question): + if self.user != question.user: + Notification(notification_type=Notification.ANSWERED, + from_user=self.user, + to_user=question.user, + question=question).save() + + def notify_accepted(self, answer): + if self.user != answer.user: + Notification(notification_type=Notification.ACCEPTED_ANSWER, + from_user=self.user, + to_user=answer.user, + answer=answer).save() + + def unotify_accepted(self, answer): + if self.user != answer.user: + Notification.objects.filter( + notification_type=Notification.ACCEPTED_ANSWER, + from_user=self.user, + to_user=answer.user, + answer=answer).delete() + + +def create_user_profile(sender, instance, created, **kwargs): + if created: + Profile.objects.create(user=instance) + + +def save_user_profile(sender, instance, **kwargs): + instance.profile.save() + +post_save.connect(create_user_profile, sender=User) +post_save.connect(save_user_profile, sender=User) diff --git a/samples/development-frameworks/django/bootcamp/authentication/static/css/signup.css b/samples/development-frameworks/django/bootcamp/authentication/static/css/signup.css new file mode 100644 index 00000000..bf96699a --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/authentication/static/css/signup.css @@ -0,0 +1,41 @@ +body { + background-color: #006389; +} + +.logo { + margin: 0; + font-size: 3em; + font-weight: 200; + font-family: "Audiowide", cursive; + text-align: center; + text-shadow: 1px 1px 0 #333333; + line-height: 70px; +} + +.logo a { + color: #92c35e; +} + +.logo a:hover, .logo a:focus { + color: #92c35e; + text-decoration: none; +} + +.signup { + background-color: #f6f6f6; + width: 70%; + border-radius: 7px; + margin: 10px auto 0; +} + +.signup { + padding: 30px 20px; +} + +.signup form { + margin-top: 20px; +} + +.signup h2 { + margin: 0; +} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/authentication/templates/authentication/signup.html b/samples/development-frameworks/django/bootcamp/authentication/templates/authentication/signup.html new file mode 100644 index 00000000..3ba3355c --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/authentication/templates/authentication/signup.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} + +{% load staticfiles i18n %} + +{% block head %} + +{% endblock head %} + +{% block body %} +

Bootcamp

+ +{% endblock body %} diff --git a/samples/development-frameworks/django/bootcamp/authentication/tests.py b/samples/development-frameworks/django/bootcamp/authentication/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/authentication/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/samples/development-frameworks/django/bootcamp/authentication/views.py b/samples/development-frameworks/django/bootcamp/authentication/views.py new file mode 100644 index 00000000..b0b7464f --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/authentication/views.py @@ -0,0 +1,31 @@ +from django.shortcuts import render, redirect +from django.contrib.auth import authenticate, login +from bootcamp.authentication.forms import SignUpForm +from django.contrib.auth.models import User +from bootcamp.feeds.models import Feed + + +def signup(request): + if request.method == 'POST': + form = SignUpForm(request.POST) + if not form.is_valid(): + return render(request, 'authentication/signup.html', + {'form': form}) + + else: + username = form.cleaned_data.get('username') + email = form.cleaned_data.get('email') + password = form.cleaned_data.get('password') + User.objects.create_user(username=username, password=password, + email=email) + user = authenticate(username=username, password=password) + login(request, user) + welcome_post = u'{0} has joined the network.'.format(user.username, + user.username) + feed = Feed(user=user, post=welcome_post) + feed.save() + return redirect('/') + + else: + return render(request, 'authentication/signup.html', + {'form': SignUpForm()}) diff --git a/samples/development-frameworks/django/bootcamp/core/__init__.py b/samples/development-frameworks/django/bootcamp/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/core/forms.py b/samples/development-frameworks/django/bootcamp/core/forms.py new file mode 100644 index 00000000..8a441c17 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/forms.py @@ -0,0 +1,69 @@ +from django import forms +from django.contrib.auth.models import User + + +class ProfileForm(forms.ModelForm): + + first_name = forms.CharField( + widget=forms.TextInput(attrs={'class': 'form-control'}), + max_length=30, + required=False) + last_name = forms.CharField( + widget=forms.TextInput(attrs={'class': 'form-control'}), + max_length=30, + required=False) + job_title = forms.CharField( + widget=forms.TextInput(attrs={'class': 'form-control'}), + max_length=50, + required=False) + email = forms.CharField( + widget=forms.TextInput(attrs={'class': 'form-control'}), + max_length=75, + required=False) + url = forms.CharField( + widget=forms.TextInput(attrs={'class': 'form-control'}), + max_length=50, + required=False) + location = forms.CharField( + widget=forms.TextInput(attrs={'class': 'form-control'}), + max_length=50, + required=False) + + class Meta: + model = User + fields = ['first_name', 'last_name', 'job_title', + 'email', 'url', 'location', ] + + +class ChangePasswordForm(forms.ModelForm): + id = forms.CharField(widget=forms.HiddenInput()) + old_password = forms.CharField( + widget=forms.PasswordInput(attrs={'class': 'form-control'}), + label="Old password", + required=True) + + new_password = forms.CharField( + widget=forms.PasswordInput(attrs={'class': 'form-control'}), + label="New password", + required=True) + confirm_password = forms.CharField( + widget=forms.PasswordInput(attrs={'class': 'form-control'}), + label="Confirm new password", + required=True) + + class Meta: + model = User + fields = ['id', 'old_password', 'new_password', 'confirm_password'] + + def clean(self): + super(ChangePasswordForm, self).clean() + old_password = self.cleaned_data.get('old_password') + new_password = self.cleaned_data.get('new_password') + confirm_password = self.cleaned_data.get('confirm_password') + id = self.cleaned_data.get('id') + user = User.objects.get(pk=id) + if not user.check_password(old_password): + self._errors['old_password'] = self.error_class(['Old password don\'t match']) + if new_password and new_password != confirm_password: + self._errors['new_password'] = self.error_class(['Passwords don\'t match']) + return self.cleaned_data diff --git a/samples/development-frameworks/django/bootcamp/core/migrations/__init__.py b/samples/development-frameworks/django/bootcamp/core/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/core/models.py b/samples/development-frameworks/django/bootcamp/core/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/samples/development-frameworks/django/bootcamp/core/static/css/cover.css b/samples/development-frameworks/django/bootcamp/core/static/css/cover.css new file mode 100644 index 00000000..567298ab --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/static/css/cover.css @@ -0,0 +1,39 @@ +body { + background-color: #006389; +} + +.cover { + width: 400px; + height: 400px; + position: fixed; + margin-left: -200px; + margin-top: -200px; + top: 50%; + left: 50%; +} + +.logo { + margin: 0; + font-size: 3em; + font-weight: 200; + font-family: "Audiowide", cursive; + color: #92c35e; + text-align: center; + text-shadow: 1px 1px 0 #333333; + line-height: 70px; +} + +.login { + background-color: #f6f6f6; + width: 100%; + height: 330px; + border-radius: 7px; +} + +.login { + padding: 10px 20px; +} + +.login form { + margin-top: 20px; +} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/core/static/css/network.css b/samples/development-frameworks/django/bootcamp/core/static/css/network.css new file mode 100644 index 00000000..f275c5c6 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/static/css/network.css @@ -0,0 +1,7 @@ +.users { + margin-top: 2em; +} + +.users .panel-heading img { + margin-right: .6em; +} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/core/static/css/profile.css b/samples/development-frameworks/django/bootcamp/core/static/css/profile.css new file mode 100644 index 00000000..65885e13 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/static/css/profile.css @@ -0,0 +1,17 @@ +.profile { + margin-top: 1em; +} + +.user-profile .picture { + width: 200px; + border-radius: 5px; +} + +.user-profile ul { + padding: 0; + margin-top: .6em; +} + +.user-profile ul li { + list-style: none; +} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/core/static/js/picture.js b/samples/development-frameworks/django/bootcamp/core/static/js/picture.js new file mode 100644 index 00000000..86e8b492 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/static/js/picture.js @@ -0,0 +1,35 @@ +$(function () { + + var jcrop_api, + boundx, + boundy, + xsize = 200, + ysize = 200; + + $("#crop-picture").Jcrop({ + aspectRatio: xsize / ysize, + onSelect: updateCoords, + setSelect: [0, 0, 200, 200] + },function(){ + var bounds = this.getBounds(); + boundx = bounds[0]; + boundy = bounds[1]; + jcrop_api = this; + }); + + function updateCoords(c) { + $("#x").val(c.x); + $("#y").val(c.y); + $("#w").val(c.w); + $("#h").val(c.h); + }; + + $("#btn-upload-picture").click(function () { + $("#picture-upload-form input[name='picture']").click(); + }); + + $("#picture-upload-form input[name='picture']").change(function () { + $("#picture-upload-form").submit(); + }); + +}); diff --git a/samples/development-frameworks/django/bootcamp/core/templates/core/cover.html b/samples/development-frameworks/django/bootcamp/core/templates/core/cover.html new file mode 100644 index 00000000..307f840f --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/templates/core/cover.html @@ -0,0 +1,46 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% block head %} + +{% endblock head %} + +{% block body %} + Fork me on GitHub +
+

Bootcamp

+ {% if form.non_field_errors %} + {% for error in form.non_field_errors %} +
+ + {{ error }} +
+ {% endfor %} + {% endif %} + +
+{% endblock body %} diff --git a/samples/development-frameworks/django/bootcamp/core/templates/core/network.html b/samples/development-frameworks/django/bootcamp/core/templates/core/network.html new file mode 100644 index 00000000..6c5a5909 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/templates/core/network.html @@ -0,0 +1,50 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% block title %}{% trans 'Network' %}{% endblock %} + +{% block head %} + +{% endblock head %} + +{% block main %} + +
+ +
+ {% for user in users %} +
+
+ +
+ {% if user.profile.job_title %} +

{% trans 'Job Title' %}: {{ user.profile.job_title }}

+ {% endif %} +

{% trans 'Username' %}: {{ user.username }}

+ {% if user.profile.location %} +

{% trans 'Location' %}: {{ user.profile.location }}

+ {% endif %} + {% if user.profile.url %} +

{% trans 'Url' %}: {{ user.profile.get_url }}

+ {% endif %} +
+
+
+ {% if forloop.counter|divisibleby:3 %}
{% endif %} + {% endfor %} +
+ +
+
+ {% include 'paginator.html' with paginator=users %} +
+
+ +
+{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/core/templates/core/partial_settings_menu.html b/samples/development-frameworks/django/bootcamp/core/templates/core/partial_settings_menu.html new file mode 100644 index 00000000..c15956e5 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/templates/core/partial_settings_menu.html @@ -0,0 +1,6 @@ +{% load i18n %} + diff --git a/samples/development-frameworks/django/bootcamp/core/templates/core/password.html b/samples/development-frameworks/django/bootcamp/core/templates/core/password.html new file mode 100644 index 00000000..0783e6b0 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/templates/core/password.html @@ -0,0 +1,49 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans 'Account Settings' %}{% endblock %} + +{% block main %} + +
+
+ {% include 'core/partial_settings_menu.html' with active='password' %} +
+
+ {% if messages %} + {% for message in messages %} +
+ + {{ message }} +
+ {% endfor %} + {% endif %} +

{% trans 'Change Password' %}

+
+ {% csrf_token %} + {{ form.id }} + {% for field in form.visible_fields %} +
+ +
+ {{ field }} + {% if field.help_text %} + {{ field.help_text }} + {% endif %} + {% for error in field.errors %} + + {% endfor %} +
+
+ {% endfor %} +
+
+ +
+
+
+
+
+{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/core/templates/core/picture.html b/samples/development-frameworks/django/bootcamp/core/templates/core/picture.html new file mode 100644 index 00000000..74b60fbc --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/templates/core/picture.html @@ -0,0 +1,75 @@ +{% extends 'base.html' %} + +{% load i18n static %} + +{% block title %}{% trans 'Account Settings' %}{% endblock %} + +{% block head %} + + + +{% endblock head %} + +{% block main %} + +
+
+ {% include 'core/partial_settings_menu.html' with active='picture' %} +
+
+ {% if messages %} + {% for message in messages %} +
+ + {{ message }} +
+ {% endfor %} + {% endif %} +

{% trans 'Change Picture' %}

+ +
+ {% csrf_token %} + + +
+ + {% if uploaded_picture %} +
+ {% csrf_token %} + +
+ {% endif %} +
+
+{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/core/templates/core/profile.html b/samples/development-frameworks/django/bootcamp/core/templates/core/profile.html new file mode 100644 index 00000000..40d434da --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/templates/core/profile.html @@ -0,0 +1,55 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% block title %}{{ page_user.profile.get_screen_name }}{% endblock %} + +{% block head %} + + + + +{% endblock head %} + +{% block main %} + +
+
+ +
+

{% trans 'Last Feeds by' %} {{ page_user.profile.get_screen_name }}

+ +
    + {% for feed in feeds %} + {% include 'feeds/partial_feed.html' with feed=feed %} + {% endfor %} +
+
+ +
+
+ + + +
+
+
+
+{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/core/templates/core/settings.html b/samples/development-frameworks/django/bootcamp/core/templates/core/settings.html new file mode 100644 index 00000000..306295ae --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/templates/core/settings.html @@ -0,0 +1,62 @@ +{% extends 'base.html' %} +{% load i18n %} +{% get_available_languages as LANGUAGES %} +{% get_current_language as CURRENT_LANGUAGE %} + +{% block title %}Account Settings{% endblock %} + +{% block main %} + +
+
+ {% include 'core/partial_settings_menu.html' with active='profile' %} +
+
+ {% if messages %} + {% for message in messages %} +
+ + {{ message }} +
+ {% endfor %} + {% endif %} +

{% trans 'Edit Profile' %}

+
+ {% csrf_token %} + {% for field in form.visible_fields %} +
+ +
+ {{ field }} + {% if field.help_text %} + {{ field.help_text }} + {% endif %} + {% for error in field.errors %} + + {% endfor %} +
+
+ {% endfor %} + {% comment %} +
+ +
+ +
+
+ {% endcomment %} +
+
+ +
+
+
+
+
+{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/core/tests.py b/samples/development-frameworks/django/bootcamp/core/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/samples/development-frameworks/django/bootcamp/core/views.py b/samples/development-frameworks/django/bootcamp/core/views.py new file mode 100644 index 00000000..6fdb5451 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/core/views.py @@ -0,0 +1,158 @@ +import os +from PIL import Image + +from django.contrib.auth.models import User +from django.contrib.auth.decorators import login_required +from django.contrib import messages +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.conf import settings as django_settings +from django.shortcuts import render, redirect, get_object_or_404 + +from bootcamp.core.forms import ProfileForm, ChangePasswordForm +from bootcamp.feeds.models import Feed +from bootcamp.feeds.views import FEEDS_NUM_PAGES +from bootcamp.feeds.views import feeds + + +def home(request): + if request.user.is_authenticated(): + return feeds(request) + else: + return render(request, 'core/cover.html') + + +@login_required +def network(request): + users_list = User.objects.filter(is_active=True).order_by('username') + paginator = Paginator(users_list, 100) + page = request.GET.get('page') + try: + users = paginator.page(page) + except PageNotAnInteger: + users = paginator.page(1) + except EmptyPage: + users = paginator.page(paginator.num_pages) + return render(request, 'core/network.html', {'users': users}) + + +@login_required +def profile(request, username): + page_user = get_object_or_404(User, username=username) + all_feeds = Feed.get_feeds().filter(user=page_user) + paginator = Paginator(all_feeds, FEEDS_NUM_PAGES) + feeds = paginator.page(1) + from_feed = -1 + if feeds: + from_feed = feeds[0].id + return render(request, 'core/profile.html', { + 'page_user': page_user, + 'feeds': feeds, + 'from_feed': from_feed, + 'page': 1 + }) + + +@login_required +def settings(request): + user = request.user + if request.method == 'POST': + form = ProfileForm(request.POST) + if form.is_valid(): + user.first_name = form.cleaned_data.get('first_name') + user.last_name = form.cleaned_data.get('last_name') + user.profile.job_title = form.cleaned_data.get('job_title') + user.email = form.cleaned_data.get('email') + user.profile.url = form.cleaned_data.get('url') + user.profile.location = form.cleaned_data.get('location') + user.save() + messages.add_message(request, + messages.SUCCESS, + 'Your profile was successfully edited.') + + else: + form = ProfileForm(instance=user, initial={ + 'job_title': user.profile.job_title, + 'url': user.profile.url, + 'location': user.profile.location + }) + return render(request, 'core/settings.html', {'form': form}) + + +@login_required +def picture(request): + uploaded_picture = False + try: + if request.GET.get('upload_picture') == 'uploaded': + uploaded_picture = True + + except Exception, e: + pass + + return render(request, 'core/picture.html', + {'uploaded_picture': uploaded_picture}) + + +@login_required +def password(request): + user = request.user + if request.method == 'POST': + form = ChangePasswordForm(request.POST) + if form.is_valid(): + new_password = form.cleaned_data.get('new_password') + user.set_password(new_password) + user.save() + messages.add_message(request, messages.SUCCESS, + 'Your password was successfully changed.') + + else: + form = ChangePasswordForm(instance=user) + + return render(request, 'core/password.html', {'form': form}) + + +@login_required +def upload_picture(request): + try: + profile_pictures = django_settings.MEDIA_ROOT + '/profile_pictures/' + if not os.path.exists(profile_pictures): + os.makedirs(profile_pictures) + f = request.FILES['picture'] + filename = profile_pictures + request.user.username + '_tmp.jpg' + with open(filename, 'wb+') as destination: + for chunk in f.chunks(): + destination.write(chunk) + im = Image.open(filename) + width, height = im.size + if width > 350: + new_width = 350 + new_height = (height * 350) / width + new_size = new_width, new_height + im.thumbnail(new_size, Image.ANTIALIAS) + im.save(filename) + + return redirect('/settings/picture/?upload_picture=uploaded') + + except Exception, e: + print e + return redirect('/settings/picture/') + + +@login_required +def save_uploaded_picture(request): + try: + x = int(request.POST.get('x')) + y = int(request.POST.get('y')) + w = int(request.POST.get('w')) + h = int(request.POST.get('h')) + tmp_filename = django_settings.MEDIA_ROOT + '/profile_pictures/' + request.user.username + '_tmp.jpg' + filename = django_settings.MEDIA_ROOT + '/profile_pictures/' + request.user.username + '.jpg' + im = Image.open(tmp_filename) + cropped_im = im.crop((x, y, w+x, h+y)) + cropped_im.thumbnail((200, 200), Image.ANTIALIAS) + cropped_im.save(filename) + os.remove(tmp_filename) + + except Exception, e: + pass + + return redirect('/settings/picture/') diff --git a/samples/development-frameworks/django/bootcamp/decorators.py b/samples/development-frameworks/django/bootcamp/decorators.py new file mode 100644 index 00000000..99392382 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/decorators.py @@ -0,0 +1,13 @@ +from django.http import HttpResponseBadRequest + + +def ajax_required(f): + def wrap(request, *args, **kwargs): + if not request.is_ajax(): + return HttpResponseBadRequest() + + return f(request, *args, **kwargs) + + wrap.__doc__ = f.__doc__ + wrap.__name__ = f.__name__ + return wrap diff --git a/samples/development-frameworks/django/bootcamp/feeds/__init__.py b/samples/development-frameworks/django/bootcamp/feeds/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/feeds/migrations/0001_initial.py b/samples/development-frameworks/django/bootcamp/feeds/migrations/0001_initial.py new file mode 100644 index 00000000..e82c5f22 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/feeds/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-03-18 13:32 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Feed', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateTimeField(auto_now_add=True)), + ('post', models.TextField(max_length=255)), + ('likes', models.IntegerField(default=0)), + ('comments', models.IntegerField(default=0)), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='feeds.Feed')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('-date',), + 'verbose_name': 'Feed', + 'verbose_name_plural': 'Feeds', + }, + ), + ] diff --git a/samples/development-frameworks/django/bootcamp/feeds/migrations/__init__.py b/samples/development-frameworks/django/bootcamp/feeds/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/feeds/models.py b/samples/development-frameworks/django/bootcamp/feeds/models.py new file mode 100644 index 00000000..b9e60084 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/feeds/models.py @@ -0,0 +1,73 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.models import User +from bootcamp.activities.models import Activity +from django.utils.html import escape +import bleach + + +class Feed(models.Model): + user = models.ForeignKey(User) + date = models.DateTimeField(auto_now_add=True) + post = models.TextField(max_length=255) + parent = models.ForeignKey('Feed', null=True, blank=True) + likes = models.IntegerField(default=0) + comments = models.IntegerField(default=0) + + class Meta: + verbose_name = _('Feed') + verbose_name_plural = _('Feeds') + ordering = ('-date',) + + def __unicode__(self): + return self.post + + @staticmethod + def get_feeds(from_feed=None): + if from_feed is not None: + feeds = Feed.objects.filter(parent=None, id__lte=from_feed) + else: + feeds = Feed.objects.filter(parent=None) + return feeds + + @staticmethod + def get_feeds_after(feed): + feeds = Feed.objects.filter(parent=None, id__gt=feed) + return feeds + + def get_comments(self): + return Feed.objects.filter(parent=self).order_by('date') + + def calculate_likes(self): + likes = Activity.objects.filter(activity_type=Activity.LIKE, + feed=self.pk).count() + self.likes = likes + self.save() + return self.likes + + def get_likes(self): + likes = Activity.objects.filter(activity_type=Activity.LIKE, + feed=self.pk) + return likes + + def get_likers(self): + likes = self.get_likes() + likers = [] + for like in likes: + likers.append(like.user) + return likers + + def calculate_comments(self): + self.comments = Feed.objects.filter(parent=self).count() + self.save() + return self.comments + + def comment(self, user, post): + feed_comment = Feed(user=user, post=post, parent=self) + feed_comment.save() + self.comments = Feed.objects.filter(parent=self).count() + self.save() + return feed_comment + + def linkfy_post(self): + return bleach.linkify(escape(self.post)) diff --git a/samples/development-frameworks/django/bootcamp/feeds/static/css/feeds.css b/samples/development-frameworks/django/bootcamp/feeds/static/css/feeds.css new file mode 100644 index 00000000..07906cff --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/feeds/static/css/feeds.css @@ -0,0 +1,167 @@ +ul.stream { + margin: 0; + padding: 0; +} + +ul.stream li { + list-style: none; + border-bottom: 1px solid #eeeeee; + padding: 1em 0; +} + +ul.stream li:last-child { + border-bottom: none; +} + +ul.stream li a img.user { + width: 60px; + border-radius: 100px; + float: left; +} + +ul.stream li div.post { + margin-left: 60px; + padding: 0 0 0 1.2em; + overflow-x: auto; +} + +ul.stream li div.post h3 { + font-size: 1em; + margin: 0; + margin-bottom: .2em; +} + +ul.stream li div.post h3 small { + margin-left: .3em; + font-size: .8em; +} + +ul.stream li div.post p { + margin: 0; +} + +ul.stream li div.post div.interaction { + padding-top: .2em; +} + +ul.stream li div.post div.interaction a { + margin-right: .6em; + font-size: .8em; +} + +.stream-update { + text-align: center; + border-bottom: 1px solid #eeeeee; + display: none; +} + +.stream-update a { + display: block; + padding: .6em 0; + background-color: #f5f8fa; +} + +.stream-update a:hover { + text-decoration: none; + background-color: #e1e8ed; +} + +.compose { + display: none; + border-bottom: 1px solid #eee; + padding-left: 15px; + padding-right: 15px; +} + +.compose h2 { + font-size: 1.4em; +} + +.comments { + margin-top: .6em; + display: none; +} + +.comments ol { + margin: .8em 0 0; + padding: .2em 0; + background-color: #f4f4f4; + border-radius: 3px; + overflow-x: auto; +} + +.comments ol li { + list-style: none; + padding: 0; +} + +.comments ol li img.user-comment { + width: 35px; + border-radius: 4px; + float: left; + margin-left: 10px; +} + +.comments ol li div { + margin-left: 45px; + padding: 0 .8em; + font-size: .9em; +} + +.comments ol li { + padding: .6em .6em .6em 0; + border-bottom: none; +} + +.comments ol li h4 { + margin: 0; + margin-left: 45px; + padding: 0 0 .2em .8em; + font-size: .9em; +} + +.comments ol li h4 small { + margin-left: .3em; +} + +.empty { + margin: 0 .8em; + font-size: .9em; +} + +.load { + text-align: center; + padding-top: 1em; + border-top: 1px solid #eeeeee; + display: none; + padding: 15px 0; +} + +.loadcomment { + text-align: center; +} + +.remove-feed { + color: #bbbbbb; + font-size: .8em; + padding-top: .2em; + float: right; + cursor: pointer; +} + +.remove-feed:hover { + color: #333333; +} + +.panel-feed { + margin-top: 20px; +} + +.panel-feed .panel-body { + padding: 0; +} + +.feed-container { + padding-left: 15px; + padding-right: 15px; +} diff --git a/samples/development-frameworks/django/bootcamp/feeds/static/js/feeds.js b/samples/development-frameworks/django/bootcamp/feeds/static/js/feeds.js new file mode 100644 index 00000000..4ace6539 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/feeds/static/js/feeds.js @@ -0,0 +1,296 @@ +$(function () { + var page_title = $(document).attr("title"); + + function hide_stream_update() { + $(".stream-update").hide(); + $(".stream-update .new-posts").text(""); + $(document).attr("title", page_title); + }; + + $("body").keydown(function (evt) { + var keyCode = evt.which?evt.which:evt.keyCode; + if (evt.ctrlKey && keyCode == 80) { + $(".btn-compose").click(); + return false; + } + }); + + $("#compose-form textarea[name='post']").keydown(function (evt) { + var keyCode = evt.which?evt.which:evt.keyCode; + if (evt.ctrlKey && (keyCode == 10 || keyCode == 13)) { + $(".btn-post").click(); + } + }); + + $(".btn-compose").click(function () { + if ($(".compose").hasClass("composing")) { + $(".compose").removeClass("composing"); + $(".compose").slideUp(); + } + else { + $(".compose").addClass("composing"); + $(".compose textarea").val(""); + $(".compose").slideDown(400, function () { + $(".compose textarea").focus(); + }); + } + }); + + $(".btn-cancel-compose").click(function () { + $(".compose").slideUp(); + }); + + $(".btn-post").click(function () { + var last_feed = $(".stream li:first-child").attr("feed-id"); + if (last_feed == undefined) { + last_feed = "0"; + } + $("#compose-form input[name='last_feed']").val(last_feed); + $.ajax({ + url: '/feeds/post/', + data: $("#compose-form").serialize(), + type: 'post', + cache: false, + success: function (data) { + $("ul.stream").prepend(data); + $(".compose").slideUp(); + $(".compose").removeClass("composing"); + hide_stream_update(); + } + }); + }); + + $("ul.stream").on("click", ".like", function () { + var li = $(this).closest("li"); + var feed = $(li).attr("feed-id"); + var csrf = $(li).attr("csrf"); + $.ajax({ + url: '/feeds/like/', + data: { + 'feed': feed, + 'csrfmiddlewaretoken': csrf + }, + type: 'post', + cache: false, + success: function (data) { + if ($(".like", li).hasClass("unlike")) { + $(".like", li).removeClass("unlike"); + $(".like .text", li).text("Like"); + } + else { + $(".like", li).addClass("unlike"); + $(".like .text", li).text("Unlike"); + } + $(".like .like-count", li).text(data); + } + }); + return false; + }); + + $("ul.stream").on("click", ".comment", function () { + var post = $(this).closest(".post"); + if ($(".comments", post).hasClass("tracking")) { + $(".comments", post).slideUp(); + $(".comments", post).removeClass("tracking"); + } + else { + $(".comments", post).show(); + $(".comments", post).addClass("tracking"); + $(".comments input[name='post']", post).focus(); + var feed = $(post).closest("li").attr("feed-id"); + $.ajax({ + url: '/feeds/comment/', + data: { 'feed': feed }, + cache: false, + beforeSend: function () { + $("ol", post).html("
  • "); + }, + success: function (data) { + $("ol", post).html(data); + $(".comment-count", post).text($("ol li", post).not(".empty").length); + } + }); + } + return false; + }); + + $("ul.stream").on("keydown", ".comments input[name='post']", function (evt) { + var keyCode = evt.which?evt.which:evt.keyCode; + if (keyCode == 13) { + var form = $(this).closest("form"); + var container = $(this).closest(".comments"); + var input = $(this); + $.ajax({ + url: '/feeds/comment/', + data: $(form).serialize(), + type: 'post', + cache: false, + beforeSend: function () { + $(input).val(""); + }, + success: function (data) { + $("ol", container).html(data); + var post_container = $(container).closest(".post"); + $(".comment-count", post_container).text($("ol li", container).length); + } + }); + return false; + } + }); + + var load_feeds = function () { + if (!$("#load_feed").hasClass("no-more-feeds")) { + var page = $("#load_feed input[name='page']").val(); + var next_page = parseInt($("#load_feed input[name='page']").val()) + 1; + $("#load_feed input[name='page']").val(next_page); + $.ajax({ + url: '/feeds/load/', + data: $("#load_feed").serialize(), + cache: false, + beforeSend: function () { + $(".load").show(); + }, + success: function (data) { + if (data.length > 0) { + $("ul.stream").append(data) + } + else { + $("#load_feed").addClass("no-more-feeds"); + } + }, + complete: function () { + $(".load").hide(); + } + }); + } + }; + + $("#load_feed").bind("enterviewport", load_feeds).bullseye(); + + function check_new_feeds () { + var last_feed = $(".stream li:first-child").attr("feed-id"); + var feed_source = $("#feed_source").val(); + if (last_feed != undefined) { + $.ajax({ + url: '/feeds/check/', + data: { + 'last_feed': last_feed, + 'feed_source': feed_source + }, + cache: false, + success: function (data) { + if (parseInt(data) > 0) { + $(".stream-update .new-posts").text(data); + $(".stream-update").show(); + $(document).attr("title", "(" + data + ") " + page_title); + } + }, + complete: function() { + window.setTimeout(check_new_feeds, 30000); + } + }); + } + else { + window.setTimeout(check_new_feeds, 30000); + } + }; + check_new_feeds(); + + $(".stream-update a").click(function () { + var last_feed = $(".stream li:first-child").attr("feed-id"); + var feed_source = $("#feed_source").val(); + $.ajax({ + url: '/feeds/load_new/', + data: { + 'last_feed': last_feed, + 'feed_source': feed_source + }, + cache: false, + success: function (data) { + $("ul.stream").prepend(data); + }, + complete: function () { + hide_stream_update(); + } + }); + return false; + }); + + $("input,textarea").attr("autocomplete", "off"); + + function update_feeds () { + var first_feed = $(".stream li:first-child").attr("feed-id"); + var last_feed = $(".stream li:last-child").attr("feed-id"); + var feed_source = $("#feed_source").val(); + + if (first_feed != undefined && last_feed != undefined) { + $.ajax({ + url: '/feeds/update/', + data: { + 'first_feed': first_feed, + 'last_feed': last_feed, + 'feed_source': feed_source + }, + cache: false, + success: function (data) { + $.each(data, function(id, feed) { + var li = $("li[feed-id='" + id + "']"); + $(".like-count", li).text(feed.likes); + $(".comment-count", li).text(feed.comments); + }); + }, + complete: function () { + window.setTimeout(update_feeds, 30000); + } + }); + } + else { + window.setTimeout(update_feeds, 30000); + } + }; + update_feeds(); + + function track_comments () { + $(".tracking").each(function () { + var container = $(this); + var feed = $(this).closest("li").attr("feed-id"); + $.ajax({ + url: '/feeds/track_comments/', + data: {'feed': feed}, + cache: false, + success: function (data) { + $("ol", container).html(data); + var post_container = $(container).closest(".post"); + $(".comment-count", post_container).text($("ol li", container).length); + } + }); + }); + window.setTimeout(track_comments, 30000); + }; + track_comments(); + + $("ul.stream").on("click", ".remove-feed", function () { + var li = $(this).closest("li"); + var feed = $(li).attr("feed-id"); + var csrf = $(li).attr("csrf"); + $.ajax({ + url: '/feeds/remove/', + data: { + 'feed': feed, + 'csrfmiddlewaretoken': csrf + }, + type: 'post', + cache: false, + success: function (data) { + $(li).fadeOut(400, function () { + $(li).remove(); + }); + } + }); + }); + + $("#compose-form textarea[name='post']").keyup(function () { + $(this).count(255); + }); + +}); \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/feeds/templates/feeds/feed.html b/samples/development-frameworks/django/bootcamp/feeds/templates/feeds/feed.html new file mode 100644 index 00000000..9688eec9 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/feeds/templates/feeds/feed.html @@ -0,0 +1,22 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% load humanize %} + +{% block title %} {% trans 'Feed' %} {% endblock %} + +{% block head %} + + + +{% endblock head %} + +{% block main %} + +
      + {% include 'feeds/partial_feed.html' with feed=feed %} +
    +{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/feeds/templates/feeds/feeds.html b/samples/development-frameworks/django/bootcamp/feeds/templates/feeds/feeds.html new file mode 100644 index 00000000..29a225b0 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/feeds/templates/feeds/feeds.html @@ -0,0 +1,70 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% block head %} + + + +{% endblock head %} + +{% block main %} + +
    +
    + + + +
    +
    +

    {% trans 'Latest posts' %}

    +
    +
    + +
    +

    {% trans "Compose a new post" %}

    +
    + {% csrf_token %} + +
    + +
    +
    + + + 255 +
    +
    +
    + +
      + {% for feed in feeds %} + {% include 'feeds/partial_feed.html' with feed=feed %} + {% endfor %} +
    +
    + +
    +
    + + + +
    + +
    +
    +
    +
    + + + +{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/feeds/templates/feeds/partial_feed.html b/samples/development-frameworks/django/bootcamp/feeds/templates/feeds/partial_feed.html new file mode 100644 index 00000000..b19b9a6b --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/feeds/templates/feeds/partial_feed.html @@ -0,0 +1,44 @@ +{% load i18n %} +{% load humanize %} + +
  • +
    + +
    + {% if feed.user == user %} + + {% endif %} +

    {{ feed.user.profile.get_screen_name }} {{ feed.date|naturaltime }}

    +

    {{ feed.linkfy_post|safe }}

    + +
    +
    + {% csrf_token %} + + +
    +
      + {% comment %} Place holder to load feed comments {% endcomment %} +
    +
    +
    +
    +
  • diff --git a/samples/development-frameworks/django/bootcamp/feeds/templates/feeds/partial_feed_comments.html b/samples/development-frameworks/django/bootcamp/feeds/templates/feeds/partial_feed_comments.html new file mode 100644 index 00000000..1f775aa4 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/feeds/templates/feeds/partial_feed_comments.html @@ -0,0 +1,22 @@ +{% load humanize %} +{% load i18n %} + +{% for comment in feed.get_comments %} +
  • + {% if comment.user == user %} + + {% endif %} + + + +

    + + {{ comment.user.profile.get_screen_name }} + + {{ comment.date|naturaltime }} +

    +
    {{ comment.linkfy_post|safe }}
    +
  • +{% empty %} +
  • {% trans 'Be the first one to comment' %}
  • +{% endfor %} diff --git a/samples/development-frameworks/django/bootcamp/feeds/tests.py b/samples/development-frameworks/django/bootcamp/feeds/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/feeds/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/samples/development-frameworks/django/bootcamp/feeds/urls.py b/samples/development-frameworks/django/bootcamp/feeds/urls.py new file mode 100644 index 00000000..04e9b2bb --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/feeds/urls.py @@ -0,0 +1,19 @@ +# coding: utf-8 + +from django.conf.urls import url + +from bootcamp.feeds import views + +urlpatterns = [ + url(r'^$', views.feeds, name='feeds'), + url(r'^post/$', views.post, name='post'), + url(r'^like/$', views.like, name='like'), + url(r'^comment/$', views.comment, name='comment'), + url(r'^load/$', views.load, name='load'), + url(r'^check/$', views.check, name='check'), + url(r'^load_new/$', views.load_new, name='load_new'), + url(r'^update/$', views.update, name='update'), + url(r'^track_comments/$', views.track_comments, name='track_comments'), + url(r'^remove/$', views.remove, name='remove_feed'), + url(r'^(\d+)/$', views.feed, name='feed'), +] diff --git a/samples/development-frameworks/django/bootcamp/feeds/views.py b/samples/development-frameworks/django/bootcamp/feeds/views.py new file mode 100644 index 00000000..564add97 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/feeds/views.py @@ -0,0 +1,208 @@ +from django.shortcuts import render, get_object_or_404 +from django.http import HttpResponse, HttpResponseBadRequest,\ + HttpResponseForbidden +from bootcamp.feeds.models import Feed +from bootcamp.activities.models import Activity +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.template.loader import render_to_string +from django.template.context_processors import csrf +import json +from django.contrib.auth.decorators import login_required +from bootcamp.decorators import ajax_required + +FEEDS_NUM_PAGES = 10 + + +@login_required +def feeds(request): + all_feeds = Feed.get_feeds() + paginator = Paginator(all_feeds, FEEDS_NUM_PAGES) + feeds = paginator.page(1) + from_feed = -1 + if feeds: + from_feed = feeds[0].id + return render(request, 'feeds/feeds.html', { + 'feeds': feeds, + 'from_feed': from_feed, + 'page': 1, + }) + + +def feed(request, pk): + feed = get_object_or_404(Feed, pk=pk) + return render(request, 'feeds/feed.html', {'feed': feed}) + + +@login_required +@ajax_required +def load(request): + from_feed = request.GET.get('from_feed') + page = request.GET.get('page') + feed_source = request.GET.get('feed_source') + all_feeds = Feed.get_feeds(from_feed) + if feed_source != 'all': + all_feeds = all_feeds.filter(user__id=feed_source) + paginator = Paginator(all_feeds, FEEDS_NUM_PAGES) + try: + feeds = paginator.page(page) + except PageNotAnInteger: + return HttpResponseBadRequest() + except EmptyPage: + feeds = [] + html = u'' + csrf_token = unicode(csrf(request)['csrf_token']) + for feed in feeds: + html = u'{0}{1}'.format(html, + render_to_string('feeds/partial_feed.html', + { + 'feed': feed, + 'user': request.user, + 'csrf_token': csrf_token + })) + + return HttpResponse(html) + + +def _html_feeds(last_feed, user, csrf_token, feed_source='all'): + feeds = Feed.get_feeds_after(last_feed) + if feed_source != 'all': + feeds = feeds.filter(user__id=feed_source) + html = u'' + for feed in feeds: + html = u'{0}{1}'.format(html, + render_to_string('feeds/partial_feed.html', + { + 'feed': feed, + 'user': user, + 'csrf_token': csrf_token + })) + + return html + + +@login_required +@ajax_required +def load_new(request): + last_feed = request.GET.get('last_feed') + user = request.user + csrf_token = unicode(csrf(request)['csrf_token']) + html = _html_feeds(last_feed, user, csrf_token) + return HttpResponse(html) + + +@login_required +@ajax_required +def check(request): + last_feed = request.GET.get('last_feed') + feed_source = request.GET.get('feed_source') + feeds = Feed.get_feeds_after(last_feed) + if feed_source != 'all': + feeds = feeds.filter(user__id=feed_source) + count = feeds.count() + return HttpResponse(count) + + +@login_required +@ajax_required +def post(request): + last_feed = request.POST.get('last_feed') + user = request.user + csrf_token = unicode(csrf(request)['csrf_token']) + feed = Feed() + feed.user = user + post = request.POST['post'] + post = post.strip() + if len(post) > 0: + feed.post = post[:255] + feed.save() + html = _html_feeds(last_feed, user, csrf_token) + return HttpResponse(html) + + +@login_required +@ajax_required +def like(request): + feed_id = request.POST['feed'] + feed = Feed.objects.get(pk=feed_id) + user = request.user + like = Activity.objects.filter(activity_type=Activity.LIKE, feed=feed_id, + user=user) + if like: + user.profile.unotify_liked(feed) + like.delete() + + else: + like = Activity(activity_type=Activity.LIKE, feed=feed_id, user=user) + like.save() + user.profile.notify_liked(feed) + + return HttpResponse(feed.calculate_likes()) + + +@login_required +@ajax_required +def comment(request): + if request.method == 'POST': + feed_id = request.POST['feed'] + feed = Feed.objects.get(pk=feed_id) + post = request.POST['post'] + post = post.strip() + if len(post) > 0: + post = post[:255] + user = request.user + feed.comment(user=user, post=post) + user.profile.notify_commented(feed) + user.profile.notify_also_commented(feed) + return render(request, 'feeds/partial_feed_comments.html', + {'feed': feed}) + + else: + feed_id = request.GET.get('feed') + feed = Feed.objects.get(pk=feed_id) + return render(request, 'feeds/partial_feed_comments.html', + {'feed': feed}) + + +@login_required +@ajax_required +def update(request): + first_feed = request.GET.get('first_feed') + last_feed = request.GET.get('last_feed') + feed_source = request.GET.get('feed_source') + feeds = Feed.get_feeds().filter(id__range=(last_feed, first_feed)) + if feed_source != 'all': + feeds = feeds.filter(user__id=feed_source) + dump = {} + for feed in feeds: + dump[feed.pk] = {'likes': feed.likes, 'comments': feed.comments} + data = json.dumps(dump) + return HttpResponse(data, content_type='application/json') + + +@login_required +@ajax_required +def track_comments(request): + feed_id = request.GET.get('feed') + feed = Feed.objects.get(pk=feed_id) + return render(request, 'feeds/partial_feed_comments.html', {'feed': feed}) + + +@login_required +@ajax_required +def remove(request): + try: + feed_id = request.POST.get('feed') + feed = Feed.objects.get(pk=feed_id) + if feed.user == request.user: + likes = feed.get_likes() + parent = feed.parent + for like in likes: + like.delete() + feed.delete() + if parent: + parent.calculate_comments() + return HttpResponse() + else: + return HttpResponseForbidden() + except Exception, e: + return HttpResponseBadRequest() diff --git a/samples/development-frameworks/django/bootcamp/locale/es/LC_MESSAGES/django.po b/samples/development-frameworks/django/bootcamp/locale/es/LC_MESSAGES/django.po new file mode 100644 index 00000000..04bf63d5 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,331 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-06-05 22:50-0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: activities/templates/activities/last_notifications.html:12 +msgid "You have no unread notification" +msgstr "Usted no tiene ninguna notificación sin leer" + +#: activities/templates/activities/last_notifications.html:14 +msgid "See all" +msgstr "Ver todo" + +#: activities/templates/activities/notifications.html:26 +msgid "You have no notification" +msgstr "Usted no tiene ninguna notificación" + +#: articles/models.py:25 articles/templates/articles/article.html:13 +msgid "Article" +msgstr "Artículo" + +#: articles/models.py:26 articles/templates/articles/article.html:12 +#: articles/templates/articles/articles.html:4 +#: articles/templates/articles/articles.html:16 +#: articles/templates/articles/drafts.html:10 +#: articles/templates/articles/edit.html:10 +#: articles/templates/articles/write.html:10 templates/base.html:39 +msgid "Articles" +msgstr "Artículos" + +#: articles/models.py:68 +msgid "Tag" +msgstr "" + +#: articles/models.py:69 articles/templates/articles/drafts.html:18 +msgid "Tags" +msgstr "" + +#: articles/templates/articles/articles.html:13 +#: articles/templates/articles/write.html:11 +#, fuzzy +msgid "Write Article" +msgstr "Escribe el artículo" + +#: articles/templates/articles/articles.html:15 +#: articles/templates/articles/drafts.html:11 +#: articles/templates/articles/edit.html:11 +msgid "Drafts" +msgstr "Proyecto" + +#: articles/templates/articles/articles.html:24 +msgid "There is no published article yet" +msgstr "No hay ningún artículo publicado aún" + +#: articles/templates/articles/articles.html:24 +msgid "Be the first one to publish" +msgstr "¡Sé el primero en publicar" + +#: articles/templates/articles/articles.html:29 +msgid "Popular Tags" +msgstr "Etiquetas populares" + +#: articles/templates/articles/drafts.html:16 +msgid "Title" +msgstr "Título" + +#: articles/templates/articles/drafts.html:17 +msgid "Content" +msgstr "Contenido" + +#: articles/templates/articles/drafts.html:35 +msgid "No draft to display" +msgstr "Ningún proyecto para mostrar" + +#: articles/templates/articles/edit.html:12 +msgid "Edit" +msgstr "Editar" + +#: articles/templates/articles/edit.html:30 +#: articles/templates/articles/write.html:29 +msgid "Publish" +msgstr "Publicar" + +#: articles/templates/articles/edit.html:31 +#: articles/templates/articles/write.html:30 +msgid "Save Draft" +msgstr "Guardar proyecto" + +#: articles/templates/articles/edit.html:32 +#: articles/templates/articles/write.html:31 +#: feeds/templates/feeds/feeds.html:29 +#: questions/templates/questions/ask.html:28 +msgid "Cancel" +msgstr "Cancelar" + +#: auth/templates/auth/signup.html:11 core/templates/core/cover.html:39 +msgid "Sign up for Bootcamp" +msgstr "Inscríbete Bootcamp" + +#: auth/templates/auth/signup.html:26 +msgid "Create an account" +msgstr "Crear una cuenta" + +#: core/templates/core/cover.html:20 core/templates/core/cover.html.py:38 +msgid "Log in" +msgstr "Iniciar la sesión" + +#: core/templates/core/cover.html:24 core/templates/core/network.html:27 +msgid "Username" +msgstr "Nombre de usuario" + +#: core/templates/core/cover.html:31 +#: core/templates/core/partial_settings_menu.html:5 +msgid "Password" +msgstr "Contraseña" + +#: core/templates/core/network.html:4 templates/base.html:41 +msgid "Network" +msgstr "Red" + +#: core/templates/core/network.html:25 +msgid "Job Title" +msgstr "Título del trabajo" + +#: core/templates/core/network.html:28 +msgid "Email" +msgstr "" + +#: core/templates/core/network.html:30 +msgid "Location" +msgstr "Ubicación" + +#: core/templates/core/network.html:33 +msgid "Url" +msgstr "" + +#: core/templates/core/partial_settings_menu.html:3 +msgid "Profile" +msgstr "Perfil" + +#: core/templates/core/partial_settings_menu.html:4 +msgid "Picture" +msgstr "Foto" + +#: core/templates/core/password.html:4 core/templates/core/password.html:8 +#: core/templates/core/picture.html:4 core/templates/core/picture.html:14 +#: core/templates/core/settings.html:8 templates/base.html:50 +msgid "Account Settings" +msgstr "Configuración de la cuenta" + +#: core/templates/core/password.html:23 +msgid "Change Password" +msgstr "Cambiar contraseña" + +#: core/templates/core/password.html:43 core/templates/core/settings.html:42 +msgid "Save" +msgstr "Guardar" + +#: core/templates/core/picture.html:29 +msgid "Change Picture" +msgstr "Cambiar Foto" + +#: core/templates/core/picture.html:34 +msgid "Upload new picture" +msgstr "Subir nueva foto" + +#: core/templates/core/picture.html:45 +msgid "Crop Picture" +msgstr "Recortar foto" + +#: core/templates/core/picture.html:49 +msgid "Crop the profile picture and then click on the" +msgstr "Recortar la foto de perfil y luego haga clic en el" + +#: core/templates/core/picture.html:49 +msgid "Save Picture" +msgstr "Guardar Foto" + +#: core/templates/core/picture.html:49 +msgid "button" +msgstr "botón" + +#: core/templates/core/picture.html:64 +msgid "Close" +msgstr "Cerrar" + +#: core/templates/core/picture.html:65 +msgid "Save changes" +msgstr "Guardar cambios" + +#: core/templates/core/profile.html:37 +msgid "Last Feeds by" +msgstr "Últimos Feeds de" + +#: core/templates/core/settings.html:23 +msgid "Edit Profile" +msgstr "Editar Perfil" + +#: feeds/models.py:16 feeds/templates/feeds/feed.html:6 templates/base.html:38 +msgid "Feed" +msgstr "" + +#: feeds/models.py:17 +msgid "Feeds" +msgstr "" + +#: feeds/templates/feeds/feeds.html:12 +msgid "Press Ctrl + P to compose" +msgstr "Presione Ctrl + P para redactar" + +#: feeds/templates/feeds/feeds.html:13 +msgid "Compose" +msgstr "Redactar" + +#: feeds/templates/feeds/feeds.html:18 +msgid "Compose a new post" +msgstr "Redactar un nuevo mensaje" + +#: feeds/templates/feeds/feeds.html:27 +msgid "Post" +msgstr "Mensaje" + +#: feeds/templates/feeds/feeds.html:34 +msgid "new posts" +msgstr "mensajes nuevos" + +#: feeds/templates/feeds/partial_feed.html:8 +msgid "Click to remove this feed" +msgstr "Haga clic para eliminar esta feed" + +#: feeds/templates/feeds/partial_feed.html:16 +msgid "Unlike" +msgstr "" + +#: feeds/templates/feeds/partial_feed.html:22 +msgid "Like" +msgstr "Gustar" + +#: feeds/templates/feeds/partial_feed.html:27 +msgid "Comment" +msgstr "Comentario" + +#: feeds/templates/feeds/partial_feed.html:35 +msgid "Write a comment..." +msgstr "Escribe un comentario..." + +#: feeds/templates/feeds/partial_feed_comments.html:11 +msgid "Be the first one to comment" +msgstr "¡Sé el primero en comentar" + +#: questions/templates/questions/ask.html:6 +#: questions/templates/questions/question.html:13 +#: questions/templates/questions/questions.html:16 +msgid "Questions" +msgstr "Preguntas" + +#: questions/templates/questions/ask.html:7 +#: questions/templates/questions/questions.html:14 +msgid "Ask Question" +msgstr "Pregunte a la pregunta" + +#: questions/templates/questions/ask.html:27 +msgid "Post Your Question" +msgstr "Publicar tu pregunta" + +#: questions/templates/questions/partial_answer.html:7 +msgid "Click to up vote; click again to toggle" +msgstr "Haga clic para up vote; haga clic de nuevo para alternar" + +#: questions/templates/questions/partial_answer.html:9 +msgid "Click to down vote; click again to toggle" +msgstr "Haga clic para vote down; haga clic de nuevo para alternar" + +#: questions/templates/questions/partial_answer.html:11 +msgid "Click to unaccept the answer" +msgstr "Haga clic para inaceptable la respuesta" + +#: questions/templates/questions/partial_answer.html:15 +msgid "Click to accept the answer" +msgstr "Haga clic para aceptar la respuesta" + +#: questions/templates/questions/partial_answer.html:22 +msgid "answered" +msgstr "contestado" + +#: questions/templates/questions/question.html:14 +msgid "Question" +msgstr "Pregunta" + +#: questions/templates/questions/question.html:56 +msgid "Post Your Answer" +msgstr "Publicar tu respuesta" + +#: questions/templates/questions/questions.html:20 +msgid "All Questions" +msgstr "Todas las preguntas" + +#: questions/templates/questions/questions.html:21 +msgid "Unanswered" +msgstr "sin respuesta" + +#: questions/templates/questions/questions.html:22 +msgid "Answered" +msgstr "Contestada" + +#: questions/templates/questions/questions.html:27 +msgid "No question to display" +msgstr "No hay preguntas para mostrar" + +#: templates/base.html:40 +msgid "Q&A" +msgstr "" + +#: templates/base.html:51 +msgid "Log out" +msgstr "Finalizar la sesión" diff --git a/samples/development-frameworks/django/bootcamp/locale/jp/LC_MESSAGES/django.po b/samples/development-frameworks/django/bootcamp/locale/jp/LC_MESSAGES/django.po new file mode 100644 index 00000000..00ac2ef8 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/locale/jp/LC_MESSAGES/django.po @@ -0,0 +1,333 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-06-07 11:09-0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: activities/templates/activities/last_notifications.html:12 +msgid "You have no unread notification" +msgstr "" + +#: activities/templates/activities/last_notifications.html:14 +msgid "See all" +msgstr "" + +#: activities/templates/activities/notifications.html:26 +msgid "You have no notification" +msgstr "" + +#: articles/models.py:25 articles/templates/articles/article.html:13 +msgid "Article" +msgstr "" + +#: articles/models.py:26 articles/templates/articles/article.html:12 +#: articles/templates/articles/articles.html:4 +#: articles/templates/articles/articles.html:16 +#: articles/templates/articles/drafts.html:10 +#: articles/templates/articles/edit.html:10 +#: articles/templates/articles/write.html:10 templates/base.html:39 +msgid "Articles" +msgstr "" + +#: articles/models.py:68 +msgid "Tag" +msgstr "" + +#: articles/models.py:69 articles/templates/articles/drafts.html:18 +msgid "Tags" +msgstr "" + +#: articles/templates/articles/articles.html:13 +#: articles/templates/articles/write.html:11 +msgid "Write Article" +msgstr "" + +#: articles/templates/articles/articles.html:15 +#: articles/templates/articles/drafts.html:11 +#: articles/templates/articles/edit.html:11 +msgid "Drafts" +msgstr "" + +#: articles/templates/articles/articles.html:24 +msgid "There is no published article yet" +msgstr "" + +#: articles/templates/articles/articles.html:24 +msgid "Be the first one to publish" +msgstr "" + +#: articles/templates/articles/articles.html:29 +msgid "Popular Tags" +msgstr "" + +#: articles/templates/articles/drafts.html:16 +msgid "Title" +msgstr "" + +#: articles/templates/articles/drafts.html:17 +msgid "Content" +msgstr "" + +#: articles/templates/articles/drafts.html:35 +msgid "No draft to display" +msgstr "" + +#: articles/templates/articles/edit.html:12 +msgid "Edit" +msgstr "" + +#: articles/templates/articles/edit.html:30 +#: articles/templates/articles/write.html:29 +msgid "Publish" +msgstr "" + +#: articles/templates/articles/edit.html:31 +#: articles/templates/articles/write.html:30 +msgid "Save Draft" +msgstr "" + +#: articles/templates/articles/edit.html:32 +#: articles/templates/articles/write.html:31 +#: feeds/templates/feeds/feeds.html:29 +#: questions/templates/questions/ask.html:28 +msgid "Cancel" +msgstr "" + +#: auth/templates/auth/signup.html:11 core/templates/core/cover.html:39 +msgid "Sign up for Bootcamp" +msgstr "" + +#: auth/templates/auth/signup.html:26 +msgid "Create an account" +msgstr "" + +#: core/templates/core/cover.html:20 core/templates/core/cover.html.py:38 +msgid "Log in" +msgstr "" + +#: core/templates/core/cover.html:24 core/templates/core/network.html:27 +msgid "Username" +msgstr "" + +#: core/templates/core/cover.html:31 +#: core/templates/core/partial_settings_menu.html:5 +msgid "Password" +msgstr "" + +#: core/templates/core/network.html:4 templates/base.html:41 +msgid "Network" +msgstr "" + +#: core/templates/core/network.html:25 +msgid "Job Title" +msgstr "" + +#: core/templates/core/network.html:28 +msgid "Email" +msgstr "" + +#: core/templates/core/network.html:30 +msgid "Location" +msgstr "" + +#: core/templates/core/network.html:33 +msgid "Url" +msgstr "" + +#: core/templates/core/partial_settings_menu.html:3 +msgid "Profile" +msgstr "" + +#: core/templates/core/partial_settings_menu.html:4 +msgid "Picture" +msgstr "" + +#: core/templates/core/password.html:4 core/templates/core/password.html:8 +#: core/templates/core/picture.html:4 core/templates/core/picture.html:14 +#: core/templates/core/settings.html:10 templates/base.html:51 +msgid "Account Settings" +msgstr "" + +#: core/templates/core/password.html:23 +msgid "Change Password" +msgstr "" + +#: core/templates/core/password.html:43 core/templates/core/settings.html:54 +msgid "Save" +msgstr "" + +#: core/templates/core/picture.html:29 +msgid "Change Picture" +msgstr "" + +#: core/templates/core/picture.html:34 +msgid "Upload new picture" +msgstr "" + +#: core/templates/core/picture.html:45 +msgid "Crop Picture" +msgstr "" + +#: core/templates/core/picture.html:49 +msgid "Crop the profile picture and then click on the" +msgstr "" + +#: core/templates/core/picture.html:49 +msgid "Save Picture" +msgstr "" + +#: core/templates/core/picture.html:49 +msgid "button" +msgstr "" + +#: core/templates/core/picture.html:64 +msgid "Close" +msgstr "" + +#: core/templates/core/picture.html:65 +msgid "Save changes" +msgstr "" + +#: core/templates/core/profile.html:37 +msgid "Last Feeds by" +msgstr "" + +#: core/templates/core/settings.html:25 +msgid "Edit Profile" +msgstr "" + +#: feeds/models.py:16 feeds/templates/feeds/feed.html:6 templates/base.html:38 +msgid "Feed" +msgstr "" + +#: feeds/models.py:17 +msgid "Feeds" +msgstr "" + +#: feeds/templates/feeds/feeds.html:12 +msgid "Press Ctrl + P to compose" +msgstr "" + +#: feeds/templates/feeds/feeds.html:13 +msgid "Compose" +msgstr "" + +#: feeds/templates/feeds/feeds.html:18 +msgid "Compose a new post" +msgstr "" + +#: feeds/templates/feeds/feeds.html:27 +msgid "Post" +msgstr "" + +#: feeds/templates/feeds/feeds.html:34 +msgid "new posts" +msgstr "" + +#: feeds/templates/feeds/partial_feed.html:8 +msgid "Click to remove this feed" +msgstr "" + +#: feeds/templates/feeds/partial_feed.html:16 +msgid "Unlike" +msgstr "" + +#: feeds/templates/feeds/partial_feed.html:22 +msgid "Like" +msgstr "" + +#: feeds/templates/feeds/partial_feed.html:27 +msgid "Comment" +msgstr "" + +#: feeds/templates/feeds/partial_feed.html:35 +msgid "Write a comment..." +msgstr "" + +#: feeds/templates/feeds/partial_feed_comments.html:11 +msgid "Be the first one to comment" +msgstr "" + +#: questions/templates/questions/ask.html:6 +#: questions/templates/questions/question.html:13 +#: questions/templates/questions/questions.html:16 +msgid "Questions" +msgstr "" + +#: questions/templates/questions/ask.html:7 +#: questions/templates/questions/questions.html:14 +msgid "Ask Question" +msgstr "" + +#: questions/templates/questions/ask.html:27 +msgid "Post Your Question" +msgstr "" + +#: questions/templates/questions/partial_answer.html:7 +msgid "Click to up vote; click again to toggle" +msgstr "" + +#: questions/templates/questions/partial_answer.html:9 +msgid "Click to down vote; click again to toggle" +msgstr "" + +#: questions/templates/questions/partial_answer.html:11 +msgid "Click to unaccept the answer" +msgstr "" + +#: questions/templates/questions/partial_answer.html:15 +msgid "Click to accept the answer" +msgstr "" + +#: questions/templates/questions/partial_answer.html:22 +msgid "answered" +msgstr "" + +#: questions/templates/questions/question.html:14 +msgid "Question" +msgstr "" + +#: questions/templates/questions/question.html:56 +msgid "Post Your Answer" +msgstr "" + +#: questions/templates/questions/questions.html:20 +msgid "All Questions" +msgstr "" + +#: questions/templates/questions/questions.html:21 +msgid "Unanswered" +msgstr "" + +#: questions/templates/questions/questions.html:22 +msgid "Answered" +msgstr "" + +#: questions/templates/questions/questions.html:27 +msgid "No question to display" +msgstr "" + +#: templates/base.html:40 +msgid "Q&A" +msgstr "" + +#: templates/base.html:42 +msgid "Search" +msgstr "" + +#: templates/base.html:52 +msgid "Log out" +msgstr "" diff --git a/samples/development-frameworks/django/bootcamp/locale/pt_BR/LC_MESSAGES/django.po b/samples/development-frameworks/django/bootcamp/locale/pt_BR/LC_MESSAGES/django.po new file mode 100644 index 00000000..19d4b0a7 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/locale/pt_BR/LC_MESSAGES/django.po @@ -0,0 +1,334 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-06-05 22:50-0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: activities/templates/activities/last_notifications.html:12 +msgid "You have no unread notification" +msgstr "Você não possui nenhuma notificação não lida" + +#: activities/templates/activities/last_notifications.html:14 +msgid "See all" +msgstr "Ver tudo" + +#: activities/templates/activities/notifications.html:26 +msgid "You have no notification" +msgstr "Você não possui notificação" + +#: articles/models.py:25 articles/templates/articles/article.html:13 +#, fuzzy +msgid "Article" +msgstr "Artigo" + +#: articles/models.py:26 articles/templates/articles/article.html:12 +#: articles/templates/articles/articles.html:4 +#: articles/templates/articles/articles.html:16 +#: articles/templates/articles/drafts.html:10 +#: articles/templates/articles/edit.html:10 +#: articles/templates/articles/write.html:10 templates/base.html:39 +msgid "Articles" +msgstr "Artigos" + +#: articles/models.py:68 +msgid "Tag" +msgstr "Tag" + +#: articles/models.py:69 articles/templates/articles/drafts.html:18 +msgid "Tags" +msgstr "Tags" + +#: articles/templates/articles/articles.html:13 +#: articles/templates/articles/write.html:11 +#, fuzzy +msgid "Write Article" +msgstr "Escrever Artigo" + +#: articles/templates/articles/articles.html:15 +#: articles/templates/articles/drafts.html:11 +#: articles/templates/articles/edit.html:11 +msgid "Drafts" +msgstr "Rascunhos" + +#: articles/templates/articles/articles.html:24 +msgid "There is no published article yet" +msgstr "Não há artigo publicado ainda" + +#: articles/templates/articles/articles.html:24 +msgid "Be the first one to publish" +msgstr "Seja o primeiro a publicar" + +#: articles/templates/articles/articles.html:29 +msgid "Popular Tags" +msgstr "Tags populares" + +#: articles/templates/articles/drafts.html:16 +#, fuzzy +msgid "Title" +msgstr "Meu titulo" + +#: articles/templates/articles/drafts.html:17 +msgid "Content" +msgstr "Conteúdo" + +#: articles/templates/articles/drafts.html:35 +msgid "No draft to display" +msgstr "Nenhum rascunho para exibir" + +#: articles/templates/articles/edit.html:12 +msgid "Edit" +msgstr "Editar" + +#: articles/templates/articles/edit.html:30 +#: articles/templates/articles/write.html:29 +msgid "Publish" +msgstr "Publicar" + +#: articles/templates/articles/edit.html:31 +#: articles/templates/articles/write.html:30 +msgid "Save Draft" +msgstr "Salvar rascunho" + +#: articles/templates/articles/edit.html:32 +#: articles/templates/articles/write.html:31 +#: feeds/templates/feeds/feeds.html:29 +#: questions/templates/questions/ask.html:28 +msgid "Cancel" +msgstr "Cancelar" + +#: auth/templates/auth/signup.html:11 core/templates/core/cover.html:39 +msgid "Sign up for Bootcamp" +msgstr "" + +#: auth/templates/auth/signup.html:26 +msgid "Create an account" +msgstr "Criar uma conta" + +#: core/templates/core/cover.html:20 core/templates/core/cover.html.py:38 +#, fuzzy +msgid "Log in" +msgstr "Entrar" + +#: core/templates/core/cover.html:24 core/templates/core/network.html:27 +msgid "Username" +msgstr "Nome de usuário" + +#: core/templates/core/cover.html:31 +#: core/templates/core/partial_settings_menu.html:5 +msgid "Password" +msgstr "Senha" + +#: core/templates/core/network.html:4 templates/base.html:41 +msgid "Network" +msgstr "Rede" + +#: core/templates/core/network.html:25 +#, fuzzy +msgid "Job Title" +msgstr "Cargo" + +#: core/templates/core/network.html:28 +msgid "Email" +msgstr "" + +#: core/templates/core/network.html:30 +msgid "Location" +msgstr "Localização" + +#: core/templates/core/network.html:33 +msgid "Url" +msgstr "" + +#: core/templates/core/partial_settings_menu.html:3 +msgid "Profile" +msgstr "Perfil" + +#: core/templates/core/partial_settings_menu.html:4 +msgid "Picture" +msgstr "Foto" + +#: core/templates/core/password.html:4 core/templates/core/password.html:8 +#: core/templates/core/picture.html:4 core/templates/core/picture.html:14 +#: core/templates/core/settings.html:8 templates/base.html:50 +msgid "Account Settings" +msgstr "Configuração da conta" + +#: core/templates/core/password.html:23 +msgid "Change Password" +msgstr "Trocar senha" + +#: core/templates/core/password.html:43 core/templates/core/settings.html:42 +msgid "Save" +msgstr "Salvar" + +#: core/templates/core/picture.html:29 +msgid "Change Picture" +msgstr "Trocar foto" + +#: core/templates/core/picture.html:34 +msgid "Upload new picture" +msgstr "Carregar nova foto" + +#: core/templates/core/picture.html:45 +msgid "Crop Picture" +msgstr "Recortar foto" + +#: core/templates/core/picture.html:49 +msgid "Crop the profile picture and then click on the" +msgstr "Recorte a foto do seu perfil, e em seguida, clique no" + +#: core/templates/core/picture.html:49 +msgid "Save Picture" +msgstr "Salvar foto" + +#: core/templates/core/picture.html:49 +msgid "button" +msgstr "botão" + +#: core/templates/core/picture.html:64 +msgid "Close" +msgstr "Fechar" + +#: core/templates/core/picture.html:65 +msgid "Save changes" +msgstr "Salvar alterações" + +#: core/templates/core/profile.html:37 +msgid "Last Feeds by" +msgstr "Últimos Feeds por" + +#: core/templates/core/settings.html:23 +msgid "Edit Profile" +msgstr "Editar Perfil" + +#: feeds/models.py:16 feeds/templates/feeds/feed.html:6 templates/base.html:38 +msgid "Feed" +msgstr "" + +#: feeds/models.py:17 +msgid "Feeds" +msgstr "" + +#: feeds/templates/feeds/feeds.html:12 +msgid "Press Ctrl + P to compose" +msgstr "Pressione Ctrl + P para escrever" + +#: feeds/templates/feeds/feeds.html:13 +msgid "Compose" +msgstr "Escrever" + +#: feeds/templates/feeds/feeds.html:18 +msgid "Compose a new post" +msgstr "Escrever uma nova postagem" + +#: feeds/templates/feeds/feeds.html:27 +msgid "Post" +msgstr "Postagem" + +#: feeds/templates/feeds/feeds.html:34 +msgid "new posts" +msgstr "novas postagens" + +#: feeds/templates/feeds/partial_feed.html:8 +msgid "Click to remove this feed" +msgstr "Clique para remover o feed" + +#: feeds/templates/feeds/partial_feed.html:16 +msgid "Unlike" +msgstr "Não gostei" + +#: feeds/templates/feeds/partial_feed.html:22 +msgid "Like" +msgstr "Gostei" + +#: feeds/templates/feeds/partial_feed.html:27 +msgid "Comment" +msgstr "Comentário" + +#: feeds/templates/feeds/partial_feed.html:35 +msgid "Write a comment..." +msgstr "Escrever um comentário" + +#: feeds/templates/feeds/partial_feed_comments.html:11 +msgid "Be the first one to comment" +msgstr "Seja o primeiro a comentar" + +#: questions/templates/questions/ask.html:6 +#: questions/templates/questions/question.html:13 +#: questions/templates/questions/questions.html:16 +msgid "Questions" +msgstr "Perguntas" + +#: questions/templates/questions/ask.html:7 +#: questions/templates/questions/questions.html:14 +msgid "Ask Question" +msgstr "Pergunte" + +#: questions/templates/questions/ask.html:27 +msgid "Post Your Question" +msgstr "Poste sua pergunta" + +#: questions/templates/questions/partial_answer.html:7 +msgid "Click to up vote; click again to toggle" +msgstr "Clique para votar; clique novamente para alterar" + +#: questions/templates/questions/partial_answer.html:9 +msgid "Click to down vote; click again to toggle" +msgstr "Clique para votar; clique novamente para alterar" + +#: questions/templates/questions/partial_answer.html:11 +msgid "Click to unaccept the answer" +msgstr "" + +#: questions/templates/questions/partial_answer.html:15 +msgid "Click to accept the answer" +msgstr "Clique caso não queira aceitar essa resposta" + +#: questions/templates/questions/partial_answer.html:22 +msgid "answered" +msgstr "respondidas" + +#: questions/templates/questions/question.html:14 +msgid "Question" +msgstr "Pergunta" + +#: questions/templates/questions/question.html:56 +msgid "Post Your Answer" +msgstr "Postar sua resposta" + +#: questions/templates/questions/questions.html:20 +msgid "All Questions" +msgstr "Todas as perguntas" + +#: questions/templates/questions/questions.html:21 +msgid "Unanswered" +msgstr "Sem resposta" + +#: questions/templates/questions/questions.html:22 +msgid "Answered" +msgstr "Respondida" + +#: questions/templates/questions/questions.html:27 +msgid "No question to display" +msgstr "Nenhuma pergunta para exibir" + +#: templates/base.html:40 +msgid "Q&A" +msgstr "" + +#: templates/base.html:51 +msgid "Log out" +msgstr "Sair" diff --git a/samples/development-frameworks/django/bootcamp/locale/zh_CN/LC_MESSAGES/django.po b/samples/development-frameworks/django/bootcamp/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 00000000..b873306f --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,331 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-06-05 22:50-0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: activities/templates/activities/last_notifications.html:12 +msgid "You have no unread notification" +msgstr "您有未阅读的信息" + +#: activities/templates/activities/last_notifications.html:14 +msgid "See all" +msgstr "查看所有" + +#: activities/templates/activities/notifications.html:26 +msgid "You have no notification" +msgstr "您没有消息" + +#: articles/models.py:25 articles/templates/articles/article.html:13 +msgid "Article" +msgstr "文章" + +#: articles/models.py:26 articles/templates/articles/article.html:12 +#: articles/templates/articles/articles.html:4 +#: articles/templates/articles/articles.html:16 +#: articles/templates/articles/drafts.html:10 +#: articles/templates/articles/edit.html:10 +#: articles/templates/articles/write.html:10 templates/base.html:39 +msgid "Articles" +msgstr "文章" + +#: articles/models.py:68 +msgid "Tag" +msgstr "标签" + +#: articles/models.py:69 articles/templates/articles/drafts.html:18 +msgid "Tags" +msgstr "标签" + +#: articles/templates/articles/articles.html:13 +#: articles/templates/articles/write.html:11 +#, fuzzy +msgid "Write Article" +msgstr "新建文章" + +#: articles/templates/articles/articles.html:15 +#: articles/templates/articles/drafts.html:11 +#: articles/templates/articles/edit.html:11 +msgid "Drafts" +msgstr "草稿" + +#: articles/templates/articles/articles.html:24 +msgid "There is no published article yet" +msgstr "目前还没有发布任何文章" + +#: articles/templates/articles/articles.html:24 +msgid "Be the first one to publish" +msgstr "来做一个第一人吧~" + +#: articles/templates/articles/articles.html:29 +msgid "Popular Tags" +msgstr "热门标签" + +#: articles/templates/articles/drafts.html:16 +msgid "Title" +msgstr "标题" + +#: articles/templates/articles/drafts.html:17 +msgid "Content" +msgstr "内容" + +#: articles/templates/articles/drafts.html:35 +msgid "No draft to display" +msgstr "无草稿显示" + +#: articles/templates/articles/edit.html:12 +msgid "Edit" +msgstr "编辑" + +#: articles/templates/articles/edit.html:30 +#: articles/templates/articles/write.html:29 +msgid "Publish" +msgstr "发布" + +#: articles/templates/articles/edit.html:31 +#: articles/templates/articles/write.html:30 +msgid "Save Draft" +msgstr "保存草稿" + +#: articles/templates/articles/edit.html:32 +#: articles/templates/articles/write.html:31 +#: feeds/templates/feeds/feeds.html:29 +#: questions/templates/questions/ask.html:28 +msgid "Cancel" +msgstr "取消" + +#: auth/templates/auth/signup.html:11 core/templates/core/cover.html:39 +msgid "Sign up for Bootcamp" +msgstr "注册Bootcamp" + +#: auth/templates/auth/signup.html:26 +msgid "Create an account" +msgstr "创建一个账户" + +#: core/templates/core/cover.html:20 core/templates/core/cover.html.py:38 +msgid "Log in" +msgstr "登录" + +#: core/templates/core/cover.html:24 core/templates/core/network.html:27 +msgid "Username" +msgstr "用户名" + +#: core/templates/core/cover.html:31 +#: core/templates/core/partial_settings_menu.html:5 +msgid "Password" +msgstr "密码" + +#: core/templates/core/network.html:4 templates/base.html:41 +msgid "Network" +msgstr "网络" + +#: core/templates/core/network.html:25 +msgid "Job Title" +msgstr "职位" + +#: core/templates/core/network.html:28 +msgid "Email" +msgstr "邮箱" + +#: core/templates/core/network.html:30 +msgid "Location" +msgstr "位置" + +#: core/templates/core/network.html:33 +msgid "Url" +msgstr "地址" + +#: core/templates/core/partial_settings_menu.html:3 +msgid "Profile" +msgstr "资料" + +#: core/templates/core/partial_settings_menu.html:4 +msgid "Picture" +msgstr "图片" + +#: core/templates/core/password.html:4 core/templates/core/password.html:8 +#: core/templates/core/picture.html:4 core/templates/core/picture.html:14 +#: core/templates/core/settings.html:8 templates/base.html:50 +msgid "Account Settings" +msgstr "账户设置" + +#: core/templates/core/password.html:23 +msgid "Change Password" +msgstr "修改密码" + +#: core/templates/core/password.html:43 core/templates/core/settings.html:42 +msgid "Save" +msgstr "保存" + +#: core/templates/core/picture.html:29 +msgid "Change Picture" +msgstr "修改图片" + +#: core/templates/core/picture.html:34 +msgid "Upload new picture" +msgstr "上传新头像" + +#: core/templates/core/picture.html:45 +msgid "Crop Picture" +msgstr "修剪头像" + +#: core/templates/core/picture.html:49 +msgid "Crop the profile picture and then click on the" +msgstr "修剪头像图片然后点击" + +#: core/templates/core/picture.html:49 +msgid "Save Picture" +msgstr "保存头像" + +#: core/templates/core/picture.html:49 +msgid "button" +msgstr "按钮" + +#: core/templates/core/picture.html:64 +msgid "Close" +msgstr "关闭" + +#: core/templates/core/picture.html:65 +msgid "Save changes" +msgstr "保存修改" + +#: core/templates/core/profile.html:37 +msgid "Last Feeds by" +msgstr "最后一条动弹" + +#: core/templates/core/settings.html:23 +msgid "Edit Profile" +msgstr "编辑资料" + +#: feeds/models.py:16 feeds/templates/feeds/feed.html:6 templates/base.html:38 +msgid "Feed" +msgstr "动弹" + +#: feeds/models.py:17 +msgid "Feeds" +msgstr "动弹" + +#: feeds/templates/feeds/feeds.html:12 +msgid "Press Ctrl + P to compose" +msgstr "按下 Ctrl+P 来发布" + +#: feeds/templates/feeds/feeds.html:13 +msgid "Compose" +msgstr "创建" + +#: feeds/templates/feeds/feeds.html:18 +msgid "Compose a new post" +msgstr "创建一篇新文章" + +#: feeds/templates/feeds/feeds.html:27 +msgid "Post" +msgstr "发布" + +#: feeds/templates/feeds/feeds.html:34 +msgid "new posts" +msgstr "新文章" + +#: feeds/templates/feeds/partial_feed.html:8 +msgid "Click to remove this feed" +msgstr "点击删除这条动弹" + +#: feeds/templates/feeds/partial_feed.html:16 +msgid "Unlike" +msgstr "不喜欢" + +#: feeds/templates/feeds/partial_feed.html:22 +msgid "Like" +msgstr "喜欢" + +#: feeds/templates/feeds/partial_feed.html:27 +msgid "Comment" +msgstr "评论" + +#: feeds/templates/feeds/partial_feed.html:35 +msgid "Write a comment..." +msgstr "写一条评论" + +#: feeds/templates/feeds/partial_feed_comments.html:11 +msgid "Be the first one to comment" +msgstr "写第一条评论" + +#: questions/templates/questions/ask.html:6 +#: questions/templates/questions/question.html:13 +#: questions/templates/questions/questions.html:16 +msgid "Questions" +msgstr "问答" + +#: questions/templates/questions/ask.html:7 +#: questions/templates/questions/questions.html:14 +msgid "Ask Question" +msgstr "提问" + +#: questions/templates/questions/ask.html:27 +msgid "Post Your Question" +msgstr "提交问题" + +#: questions/templates/questions/partial_answer.html:7 +msgid "Click to up vote; click again to toggle" +msgstr "点击推荐,再次点击取消" + +#: questions/templates/questions/partial_answer.html:9 +msgid "Click to down vote; click again to toggle" +msgstr "点击不推荐,再次点击取消" + +#: questions/templates/questions/partial_answer.html:11 +msgid "Click to unaccept the answer" +msgstr "点击不接受答案" + +#: questions/templates/questions/partial_answer.html:15 +msgid "Click to accept the answer" +msgstr "点击同意该答案" + +#: questions/templates/questions/partial_answer.html:22 +msgid "answered" +msgstr "已回答" + +#: questions/templates/questions/question.html:14 +msgid "Question" +msgstr "问题" + +#: questions/templates/questions/question.html:56 +msgid "Post Your Answer" +msgstr "提交您的问题" + +#: questions/templates/questions/questions.html:20 +msgid "All Questions" +msgstr "所有问题" + +#: questions/templates/questions/questions.html:21 +msgid "Unanswered" +msgstr "寻求答案" + +#: questions/templates/questions/questions.html:22 +msgid "Answered" +msgstr "已解决" + +#: questions/templates/questions/questions.html:27 +msgid "No question to display" +msgstr "没有问题显示" + +#: templates/base.html:40 +msgid "Q&A" +msgstr "问答" + +#: templates/base.html:51 +msgid "Log out" +msgstr "登出" diff --git a/samples/development-frameworks/django/bootcamp/messenger/__init__.py b/samples/development-frameworks/django/bootcamp/messenger/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/messenger/migrations/0001_initial.py b/samples/development-frameworks/django/bootcamp/messenger/migrations/0001_initial.py new file mode 100644 index 00000000..1b1d125c --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-03-18 13:32 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Message', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('message', models.TextField(blank=True, max_length=1000)), + ('date', models.DateTimeField(auto_now_add=True)), + ('is_read', models.BooleanField(default=False)), + ('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), + ('from_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('date',), + 'db_table': 'messages_message', + 'verbose_name': 'Message', + 'verbose_name_plural': 'Messages', + }, + ), + ] diff --git a/samples/development-frameworks/django/bootcamp/messenger/migrations/__init__.py b/samples/development-frameworks/django/bootcamp/messenger/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/messenger/models.py b/samples/development-frameworks/django/bootcamp/messenger/models.py new file mode 100644 index 00000000..69c83bbb --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/models.py @@ -0,0 +1,56 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.models import User +from django.db.models import Max + + +class Message(models.Model): + user = models.ForeignKey(User, related_name='+') + message = models.TextField(max_length=1000, blank=True) + date = models.DateTimeField(auto_now_add=True) + conversation = models.ForeignKey(User, related_name='+') + from_user = models.ForeignKey(User, related_name='+') + is_read = models.BooleanField(default=False) + + class Meta: + verbose_name = _('Message') + verbose_name_plural = _('Messages') + ordering = ('date',) + db_table = 'messages_message' + + def __unicode__(self): + return self.message + + @staticmethod + def send_message(from_user, to_user, message): + message = message[:1000] + current_user_message = Message(from_user=from_user, + message=message, + user=from_user, + conversation=to_user, + is_read=True) + current_user_message.save() + Message(from_user=from_user, + conversation=from_user, + message=message, + user=to_user).save() + + return current_user_message + + @staticmethod + def get_conversations(user): + conversations = Message.objects.filter( + user=user).values('conversation').annotate( + last=Max('date')).order_by('-last') + users = [] + for conversation in conversations: + users.append({ + 'user': User.objects.get(pk=conversation['conversation']), + 'last': conversation['last'], + 'unread': Message.objects.filter(user=user, + conversation__pk=conversation[ + 'conversation'], + is_read=False).count(), + }) + + return users diff --git a/samples/development-frameworks/django/bootcamp/messenger/static/css/messages.css b/samples/development-frameworks/django/bootcamp/messenger/static/css/messages.css new file mode 100644 index 00000000..76571bee --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/static/css/messages.css @@ -0,0 +1,74 @@ +.conversation { + margin: 0; + padding: 0; +} + +.conversation li { + list-style: none; + padding: .6em 0; +} + +.conversation li h5 { + margin: 0; + margin-bottom: .2em; +} + +.conversation li div { + margin-left: 50px; +} + +.conversation .picture { + width: 40px; + border-radius: 5px; + float: left; +} + +.conversation-portrait { + width: 20px; + border-radius: 3px; + margin-right: 5px; +} + +.typeahead, .tt-query, .tt-hint { + border: 1px solid #CCCCCC; + border-radius: 5px; + font-size: 1.2em; + height: 35px; + line-height: 35px; + outline: medium none; + padding: 4px 12px; + width: 300px; +} +.typeahead { + background-color: #FFFFFF; +} +.typeahead:focus { + border: 2px solid #0097CF; +} +.tt-query { + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset; +} +.tt-hint { + color: #999999; +} +.tt-dropdown-menu { + background-color: #FFFFFF; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 8px; + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + margin-top: 8px; + padding: 8px 0; + width: 300px; +} +.tt-suggestion { + font-size: 1.2em; + line-height: 24px; + padding: 3px 20px; +} +.tt-suggestion.tt-cursor { + background-color: #0097CF; + color: #FFFFFF; +} +.tt-suggestion p { + margin: 0; +} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/messenger/static/js/check_messages.js b/samples/development-frameworks/django/bootcamp/messenger/static/js/check_messages.js new file mode 100644 index 00000000..70863e18 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/static/js/check_messages.js @@ -0,0 +1,15 @@ +$(function () { + function check_messages() { + $.ajax({ + url: '/messages/check/', + cache: false, + success: function (data) { + $("#unread-count").text(data); + }, + complete: function () { + window.setTimeout(check_messages, 60000); + } + }); + }; + check_messages(); +}); \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/messenger/static/js/messages.js b/samples/development-frameworks/django/bootcamp/messenger/static/js/messages.js new file mode 100644 index 00000000..05fc9fb8 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/static/js/messages.js @@ -0,0 +1,16 @@ +$(function () { + $("#send").submit(function () { + $.ajax({ + url: '/messages/send/', + data: $("#send").serialize(), + cache: false, + type: 'post', + success: function (data) { + $(".send-message").before(data); + $("input[name='message']").val(''); + $("input[name='message']").focus(); + } + }); + return false; + }); +}); \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/messenger/static/js/messages.typehead.js b/samples/development-frameworks/django/bootcamp/messenger/static/js/messages.typehead.js new file mode 100644 index 00000000..89d6869f --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/static/js/messages.typehead.js @@ -0,0 +1,32 @@ +$(function () { + var substringMatcher = function(strs) { + return function findMatches(q, cb) { + var matches, substringRegex; + matches = []; + substrRegex = new RegExp(q, 'i'); + $.each(strs, function(i, str) { + if (substrRegex.test(str)) { + matches.push({ value: str }); + } + }); + cb(matches); + }; + }; + + $.ajax({ + url: '/messages/users/', + cache: false, + success: function (data) { + $('#to').typeahead({ + hint: true, + highlight: true, + minLength: 1 + }, + { + name: 'data', + displayKey: 'value', + source: substringMatcher(data) + }); + } + }); +}); \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/base_messages.html b/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/base_messages.html new file mode 100644 index 00000000..3ec1e078 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/base_messages.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% load staticfiles %} + +{% block head %} + + + +{% endblock head %} + +{% block main %} + +
    +
    + {% include 'messenger/includes/partial_conversations.html' with conversations=conversations active=active %} +
    +
    + {% block container %} + {% endblock container %} +
    +
    +{% endblock main %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/inbox.html b/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/inbox.html new file mode 100644 index 00000000..f38282ca --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/inbox.html @@ -0,0 +1,28 @@ +{% extends 'messenger/base_messages.html' %} +{% load i18n %} + +{% block title %}{% trans 'Inbox' %}{% endblock %} + +{% block page_header %}{% trans 'Inbox' %}{% endblock %} + +{% block container %} + {% if messages %} +
      + {% for message in messages %} + {% include 'messenger/includes/partial_message.html' with message=message %} + {% endfor %} +
    • + +
      +
      + {% csrf_token %} + + +
      +
      +
    • +
    + {% else %} +

    Your inbox is empty!

    + {% endif %} +{% endblock container %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/includes/partial_conversations.html b/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/includes/partial_conversations.html new file mode 100644 index 00000000..e46cc507 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/includes/partial_conversations.html @@ -0,0 +1,23 @@ +{% load i18n %} + + \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/includes/partial_message.html b/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/includes/partial_message.html new file mode 100644 index 00000000..c3d472c9 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/includes/partial_message.html @@ -0,0 +1,14 @@ +
  • + +
    +
    + + {{ message.date|date:'N d G:i' }} + + + {{ message.from_user.profile.get_screen_name }} + +
    + {{ message.message }} +
    +
  • \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/new.html b/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/new.html new file mode 100644 index 00000000..196e690c --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/templates/messenger/new.html @@ -0,0 +1,31 @@ +{% extends 'messenger/base_messages.html' %} +{% load staticfiles %} +{% load i18n %} + +{% block title %}{% trans 'New message' %}{% endblock %} + +{% block page_header %}{% trans 'New message' %}{% endblock %} + +{% block container %} +
    + {% csrf_token %} +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    + +{% endblock container %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/messenger/tests.py b/samples/development-frameworks/django/bootcamp/messenger/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/samples/development-frameworks/django/bootcamp/messenger/urls.py b/samples/development-frameworks/django/bootcamp/messenger/urls.py new file mode 100644 index 00000000..0bf6ce8a --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/urls.py @@ -0,0 +1,15 @@ +# coding: utf-8 + +from django.conf.urls import url +from bootcamp.messenger import views + + +urlpatterns = [ + url(r'^$', views.inbox, name='inbox'), + url(r'^new/$', views.new, name='new_message'), + url(r'^send/$', views.send, name='send_message'), + url(r'^delete/$', views.delete, name='delete_message'), + url(r'^users/$', views.users, name='users_message'), + url(r'^check/$', views.check, name='check_message'), + url(r'^(?P[^/]+)/$', views.messages, name='messages'), +] diff --git a/samples/development-frameworks/django/bootcamp/messenger/views.py b/samples/development-frameworks/django/bootcamp/messenger/views.py new file mode 100644 index 00000000..9a3644ba --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/messenger/views.py @@ -0,0 +1,127 @@ +from django.shortcuts import render, redirect +from django.http import HttpResponse, HttpResponseBadRequest +from django.contrib.auth.decorators import login_required +from bootcamp.decorators import ajax_required +from django.contrib.auth.models import User +import json +from bootcamp.messenger.models import Message + + +@login_required +def inbox(request): + conversations = Message.get_conversations(user=request.user) + active_conversation = None + messages = None + if conversations: + conversation = conversations[0] + active_conversation = conversation['user'].username + messages = Message.objects.filter(user=request.user, + conversation=conversation['user']) + messages.update(is_read=True) + for conversation in conversations: + if conversation['user'].username == active_conversation: + conversation['unread'] = 0 + + return render(request, 'messenger/inbox.html', { + 'messages': messages, + 'conversations': conversations, + 'active': active_conversation + }) + + +@login_required +def messages(request, username): + conversations = Message.get_conversations(user=request.user) + active_conversation = username + messages = Message.objects.filter(user=request.user, + conversation__username=username) + messages.update(is_read=True) + for conversation in conversations: + if conversation['user'].username == username: + conversation['unread'] = 0 + + return render(request, 'messenger/inbox.html', { + 'messages': messages, + 'conversations': conversations, + 'active': active_conversation + }) + + +@login_required +def new(request): + if request.method == 'POST': + from_user = request.user + to_user_username = request.POST.get('to') + try: + to_user = User.objects.get(username=to_user_username) + + except Exception, e: + try: + to_user_username = to_user_username[ + to_user_username.rfind('(')+1:len(to_user_username)-1] + to_user = User.objects.get(username=to_user_username) + + except Exception, e: + return redirect('/messages/new/') + + message = request.POST.get('message') + if len(message.strip()) == 0: + return redirect('/messages/new/') + + if from_user != to_user: + Message.send_message(from_user, to_user, message) + + return redirect(u'/messages/{0}/'.format(to_user_username)) + + else: + conversations = Message.get_conversations(user=request.user) + return render(request, 'messenger/new.html', + {'conversations': conversations}) + + +@login_required +@ajax_required +def delete(request): + return HttpResponse() + + +@login_required +@ajax_required +def send(request): + if request.method == 'POST': + from_user = request.user + to_user_username = request.POST.get('to') + to_user = User.objects.get(username=to_user_username) + message = request.POST.get('message') + if len(message.strip()) == 0: + return HttpResponse() + if from_user != to_user: + msg = Message.send_message(from_user, to_user, message) + return render(request, 'messenger/includes/partial_message.html', + {'message': msg}) + + return HttpResponse() + else: + return HttpResponseBadRequest() + + +@login_required +@ajax_required +def users(request): + users = User.objects.filter(is_active=True) + dump = [] + template = u'{0} ({1})' + for user in users: + if user.profile.get_screen_name() != user.username: + dump.append(template.format(user.profile.get_screen_name(), user.username)) + else: + dump.append(user.username) + data = json.dumps(dump) + return HttpResponse(data, content_type='application/json') + + +@login_required +@ajax_required +def check(request): + count = Message.objects.filter(user=request.user, is_read=False).count() + return HttpResponse(count) diff --git a/samples/development-frameworks/django/bootcamp/questions/__init__.py b/samples/development-frameworks/django/bootcamp/questions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/questions/forms.py b/samples/development-frameworks/django/bootcamp/questions/forms.py new file mode 100644 index 00000000..51adf462 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/forms.py @@ -0,0 +1,32 @@ +from django import forms +from bootcamp.questions.models import Question, Answer + + +class QuestionForm(forms.ModelForm): + title = forms.CharField( + widget=forms.TextInput(attrs={'class': 'form-control'}), + max_length=255) + description = forms.CharField( + widget=forms.Textarea(attrs={'class': 'form-control'}), + max_length=2000) + tags = forms.CharField( + widget=forms.TextInput(attrs={'class': 'form-control'}), + max_length=255, + required=False, + help_text='Use spaces to separate the tags, such as "asp.net mvc5 javascript"') + + class Meta: + model = Question + fields = ['title', 'description', 'tags'] + + +class AnswerForm(forms.ModelForm): + question = forms.ModelChoiceField(widget=forms.HiddenInput(), + queryset=Question.objects.all()) + description = forms.CharField( + widget=forms.Textarea(attrs={'class': 'form-control', 'rows': '4'}), + max_length=2000) + + class Meta: + model = Answer + fields = ['question', 'description'] diff --git a/samples/development-frameworks/django/bootcamp/questions/migrations/0001_initial.py b/samples/development-frameworks/django/bootcamp/questions/migrations/0001_initial.py new file mode 100644 index 00000000..7ab5b0e1 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/migrations/0001_initial.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-03-18 13:32 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Answer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.TextField(max_length=2000)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('update_date', models.DateTimeField(blank=True, null=True)), + ('votes', models.IntegerField(default=0)), + ('is_accepted', models.BooleanField(default=False)), + ], + options={ + 'ordering': ('-is_accepted', '-votes', 'create_date'), + 'verbose_name': 'Answer', + 'verbose_name_plural': 'Answers', + }, + ), + migrations.CreateModel( + name='Question', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('description', models.TextField(max_length=2000)), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('update_date', models.DateTimeField(auto_now_add=True)), + ('favorites', models.IntegerField(default=0)), + ('has_accepted_answer', models.BooleanField(default=False)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('-update_date',), + 'verbose_name': 'Question', + 'verbose_name_plural': 'Questions', + }, + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tag', models.CharField(max_length=50)), + ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='questions.Question')), + ], + options={ + 'verbose_name': 'Tag', + 'verbose_name_plural': 'Tags', + }, + ), + migrations.AddField( + model_name='answer', + name='question', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='questions.Question'), + ), + migrations.AddField( + model_name='answer', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterUniqueTogether( + name='tag', + unique_together=set([('tag', 'question')]), + ), + migrations.AlterIndexTogether( + name='tag', + index_together=set([('tag', 'question')]), + ), + ] diff --git a/samples/development-frameworks/django/bootcamp/questions/migrations/__init__.py b/samples/development-frameworks/django/bootcamp/questions/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/questions/models.py b/samples/development-frameworks/django/bootcamp/questions/models.py new file mode 100644 index 00000000..3936c7aa --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/models.py @@ -0,0 +1,147 @@ +from django.db import models +from django.contrib.auth.models import User +from bootcamp.activities.models import Activity +import markdown + + +class Question(models.Model): + user = models.ForeignKey(User) + title = models.CharField(max_length=255) + description = models.TextField(max_length=2000) + create_date = models.DateTimeField(auto_now_add=True) + update_date = models.DateTimeField(auto_now_add=True) + favorites = models.IntegerField(default=0) + has_accepted_answer = models.BooleanField(default=False) + + class Meta: + verbose_name = 'Question' + verbose_name_plural = 'Questions' + ordering = ('-update_date',) + + def __unicode__(self): + return self.title + + @staticmethod + def get_unanswered(): + return Question.objects.filter(has_accepted_answer=False) + + @staticmethod + def get_answered(): + return Question.objects.filter(has_accepted_answer=True) + + def get_answers(self): + return Answer.objects.filter(question=self) + + def get_answers_count(self): + return Answer.objects.filter(question=self).count() + + def get_accepted_answer(self): + return Answer.objects.get(question=self, is_accepted=True) + + def get_description_as_markdown(self): + return markdown.markdown(self.description, safe_mode='escape') + + def get_description_preview(self): + if len(self.description) > 255: + return u'{0}...'.format(self.description[:255]) + else: + return self.description + + def get_description_preview_as_markdown(self): + return markdown.markdown(self.get_description_preview(), + safe_mode='escape') + + def calculate_favorites(self): + favorites = Activity.objects.filter(activity_type=Activity.FAVORITE, + question=self.pk).count() + self.favorites = favorites + self.save() + return self.favorites + + def get_favoriters(self): + favorites = Activity.objects.filter(activity_type=Activity.FAVORITE, + question=self.pk) + favoriters = [] + for favorite in favorites: + favoriters.append(favorite.user) + return favoriters + + def create_tags(self, tags): + tags = tags.strip() + tag_list = tags.split(' ') + for tag in tag_list: + t, created = Tag.objects.get_or_create(tag=tag.lower(), + question=self) + + def get_tags(self): + return Tag.objects.filter(question=self) + + +class Answer(models.Model): + user = models.ForeignKey(User) + question = models.ForeignKey(Question) + description = models.TextField(max_length=2000) + create_date = models.DateTimeField(auto_now_add=True) + update_date = models.DateTimeField(null=True, blank=True) + votes = models.IntegerField(default=0) + is_accepted = models.BooleanField(default=False) + + class Meta: + verbose_name = 'Answer' + verbose_name_plural = 'Answers' + ordering = ('-is_accepted', '-votes', 'create_date',) + + def __unicode__(self): + return self.description + + def accept(self): + answers = Answer.objects.filter(question=self.question) + for answer in answers: + answer.is_accepted = False + answer.save() + self.is_accepted = True + self.save() + self.question.has_accepted_answer = True + self.question.save() + + def calculate_votes(self): + up_votes = Activity.objects.filter(activity_type=Activity.UP_VOTE, + answer=self.pk).count() + down_votes = Activity.objects.filter(activity_type=Activity.DOWN_VOTE, + answer=self.pk).count() + self.votes = up_votes - down_votes + self.save() + return self.votes + + def get_up_voters(self): + votes = Activity.objects.filter(activity_type=Activity.UP_VOTE, + answer=self.pk) + voters = [] + for vote in votes: + voters.append(vote.user) + return voters + + def get_down_voters(self): + votes = Activity.objects.filter(activity_type=Activity.DOWN_VOTE, + answer=self.pk) + voters = [] + for vote in votes: + voters.append(vote.user) + return voters + + def get_description_as_markdown(self): + return markdown.markdown(self.description, safe_mode='escape') + + +class Tag(models.Model): + tag = models.CharField(max_length=50) + question = models.ForeignKey(Question) + + class Meta: + verbose_name = 'Tag' + verbose_name_plural = 'Tags' + unique_together = (('tag', 'question'),) + index_together = [['tag', 'question'], ] + + def __unicode__(self): + return self.tag diff --git a/samples/development-frameworks/django/bootcamp/questions/static/css/questions.css b/samples/development-frameworks/django/bootcamp/questions/static/css/questions.css new file mode 100644 index 00000000..bce657cc --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/static/css/questions.css @@ -0,0 +1,125 @@ +.questions { + margin-top: 1em; +} + +.questions .pagination { + margin: 0; +} + +.questions .user { + width: 25px; + border-radius: 3px; +} + +.questions .asked { + color: #aaaaaa; + font-size: .8em; +} + +.questions .username { + margin-left: .3em; + font-weight: 500; + font-size: 0.8em; +} + +.questions .panel-body:hover { + background-color: #f5f8fa; + cursor: pointer; +} + +.questions .question-user { + margin-bottom: .6em; +} + +.options { + text-align: center; + +} + +.options span { + display: block; + font-size: 1.2em; +} + +.options span.favorite { + font-size: 3em; + margin-top: .4em; +} + +.options span.vote { + font-size: 2em; + color: #ddd; +} + +.options span.vote:hover { + cursor: pointer; + color: #428bca; +} + +.options span.voted { + color: #333; +} + +.options span.accept { + font-size: 2.6em; + color: #dddddd; + margin-top: .2em; +} + +.options span.accept:hover { + cursor: pointer; + color: #5cb85c; +} + +.options span.accepted { + color: #5cb85c; +} + +.answer { + margin-top: 1em; +} + +.answer .answer-user, .question .question-user { + margin-bottom: .6em; +} + +.answer .answer-user .user, .question .question-user .user { + width: 40px; + border-radius: 5px; +} + +.answer .answer-user .username, .question .question-user .username { + margin-left: .4em; +} + +.answer .answered, .question .asked { + color: #aaaaaa; + margin-left: .6em; +} + +.favorite { + cursor: pointer; +} + +.favorite:hover { + color: #f0ad4e; +} + +.favorited { + color: #f0ad4e; +} + +.question-info { + text-align: center; + float: right; + padding: 0 1em; +} + +.question-info h5 { + margin: 0; + margin-bottom: .2em; +} + +.question-info .info:first-child { + margin-bottom: 1.2em; +} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/questions/static/js/questions.js b/samples/development-frameworks/django/bootcamp/questions/static/js/questions.js new file mode 100644 index 00000000..7c08deb0 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/static/js/questions.js @@ -0,0 +1,93 @@ +$(function () { + $(".question .panel-body").click(function () { + var question_id = $(this).closest(".question").attr("question-id"); + location.href = "/questions/" + question_id; + }); + + $(".accept").click(function () { + var span = $(this); + var question = $(".question").attr("question-id"); + var answer = $(this).closest(".answer").attr("answer-id"); + var csrf = $("input[name='csrfmiddlewaretoken']", $(this).closest(".answer")).val(); + $.ajax({ + url: '/questions/answer/accept/', + data: { + 'question': question, + 'answer': answer, + 'csrfmiddlewaretoken': csrf + }, + type: 'post', + cache: false, + success: function (data) { + $(".accept").removeClass("accepted"); + $(".accept").prop("title", "Click to accept the answer"); + $(span).addClass("accepted"); + $(span).prop("title", "Click to unaccept the answer"); + } + }); + }); + + $(".vote").click(function () { + var span = $(this); + var answer = $(this).closest(".answer").attr("answer-id"); + var csrf = $("input[name='csrfmiddlewaretoken']", $(this).closest(".answer")).val(); + var vote = ""; + if ($(this).hasClass("voted")) { + var vote = "R"; + } + else if ($(this).hasClass("up-vote")) { + vote = "U"; + } + else if ($(this).hasClass("down-vote")) { + vote = "D"; + } + $.ajax({ + url: '/questions/answer/vote/', + data: { + 'answer': answer, + 'vote': vote, + 'csrfmiddlewaretoken': csrf + }, + type: 'post', + cache: false, + success: function (data) { + var options = $(span).closest('.options'); + $('.vote', options).removeClass('voted'); + if (vote == 'U' || vote == 'D') { + $(span).addClass('voted'); + } + $('.votes', options).text(data); + } + }); + }); + + $(".favorite").click(function () { + var span = $(this); + var question = $(this).closest(".question").attr("question-id"); + var csrf = $("input[name='csrfmiddlewaretoken']", $(this).closest(".question")).val(); + + $.ajax({ + url: '/questions/favorite/', + data: { + 'question': question, + 'csrfmiddlewaretoken': csrf + }, + type: 'post', + cache: false, + success: function (data) { + if ($(span).hasClass("favorited")) { + $(span).removeClass("glyphicon-star") + .removeClass("favorited") + .addClass("glyphicon-star-empty"); + } + else { + $(span).removeClass("glyphicon-star-empty") + .addClass("glyphicon-star") + .addClass("favorited"); + } + $(".favorite-count").text(data); + } + }); + + }); +}); \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/questions/templates/questions/ask.html b/samples/development-frameworks/django/bootcamp/questions/templates/questions/ask.html new file mode 100644 index 00000000..99ac48e7 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/templates/questions/ask.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block main %} + +
    + {% csrf_token %} + {% for field in form.visible_fields %} +
    + +
    + {% if field.label == 'Description' %} + {% include 'markdown_editor.html' with textarea='id_description' %} + {% endif %} + {{ field }} + {% if field.help_text %} + {{ field.help_text }} + {% endif %} + {% for error in field.errors %} + + {% endfor %} +
    +
    + {% endfor %} +
    +
    + + {% trans 'Cancel' %} +
    +
    +
    +{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/questions/templates/questions/partial_answer.html b/samples/development-frameworks/django/bootcamp/questions/templates/questions/partial_answer.html new file mode 100644 index 00000000..4727fa8b --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/templates/questions/partial_answer.html @@ -0,0 +1,29 @@ +{% load i18n %} +{% load humanize %} + +
    + {% csrf_token %} +
    + + {{ answer.votes }} + + {% if answer.is_accepted and user == question.user %} + + {% elif answer.is_accepted %} + + {% elif user == question.user %} + + {% endif %} +
    +
    +
    + + {{ answer.user.profile.get_screen_name }} + {% trans "answered" %} {{ answer.create_date|naturaltime }} +
    +
    + {{ answer.get_description_as_markdown|safe }} +
    +
    +
    +
    diff --git a/samples/development-frameworks/django/bootcamp/questions/templates/questions/partial_question.html b/samples/development-frameworks/django/bootcamp/questions/templates/questions/partial_question.html new file mode 100644 index 00000000..8bf514c5 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/templates/questions/partial_question.html @@ -0,0 +1,39 @@ +{% load humanize %} + +
    +
    +

    + {% if question.has_accepted_answer %} + + {% endif %} + {{ question.title }} +

    +
    +
    +
    +
    +
    Answers
    + {{ question.get_answers_count }} +
    +
    +
    Favorites
    + {{ question.favorites }} +
    +
    +
    + + {{ question.user.profile.get_screen_name }} + asked {{ question.update_date|naturaltime }} +
    +
    + {{ question.get_description_preview_as_markdown|safe }} +
    + {% if question.get_tags %} +

    + {% for tag in question.get_tags %} + {{ tag }} + {% endfor %} +

    + {% endif %} +
    +
    \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/questions/templates/questions/question.html b/samples/development-frameworks/django/bootcamp/questions/templates/questions/question.html new file mode 100644 index 00000000..262184d9 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/templates/questions/question.html @@ -0,0 +1,64 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% load humanize %} + +{% block head %} + + +{% endblock head %} + +{% block main %} + +
    + {% csrf_token %} +
    + {% if user in question.get_favoriters %} + + {% else %} + + {% endif %} + {{ question.favorites }} +
    +
    +

    {{ question.title }}

    +
    + + {{ question.user.profile.get_screen_name }} + asked {{ question.update_date|naturaltime }} +
    +
    + {{ question.get_description_as_markdown|safe }} +
    + {% if question.get_tag_list %} +

    + {% for tag in question.get_tag_list %} + {{ tag }} + {% endfor %} +

    + {% endif %} +
    +
    + +
    + {% for answer in question.get_answers %} + {% include 'questions/partial_answer.html' with question=question answer=answer %} + {% endfor %} +

    Your Answer

    +
    + {% csrf_token %} + {{ form.question }} +
    + {% include 'markdown_editor.html' with textarea='id_description' %} + {{ form.description }} +
    +
    + +
    +
    +
    +{% endblock main %} diff --git a/samples/development-frameworks/django/bootcamp/questions/templates/questions/questions.html b/samples/development-frameworks/django/bootcamp/questions/templates/questions/questions.html new file mode 100644 index 00000000..e41c9a42 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/templates/questions/questions.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} + +{% block title %}Questions{% endblock %} + +{% block head %} + + +{% endblock head %} + +{% block main %} + +
    + + {% for question in questions %} + {% include 'questions/partial_question.html' with question=question %} + {% empty %} +

    {% trans "No question to display" %}

    + {% endfor %} + {% include 'paginator.html' with paginator=questions %} +
    +{% endblock main %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/questions/tests.py b/samples/development-frameworks/django/bootcamp/questions/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/samples/development-frameworks/django/bootcamp/questions/urls.py b/samples/development-frameworks/django/bootcamp/questions/urls.py new file mode 100644 index 00000000..38632d52 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/urls.py @@ -0,0 +1,18 @@ +# coding: utf-8 + +from django.conf.urls import url + +from bootcamp.questions import views + +urlpatterns = [ + url(r'^$', views.questions, name='questions'), + url(r'^answered/$', views.answered, name='answered'), + url(r'^unanswered/$', views.unanswered, name='unanswered'), + url(r'^all/$', views.all, name='all'), + url(r'^ask/$', views.ask, name='ask'), + url(r'^favorite/$', views.favorite, name='favorite'), + url(r'^answer/$', views.answer, name='answer'), + url(r'^answer/accept/$', views.accept, name='accept'), + url(r'^answer/vote/$', views.vote, name='vote'), + url(r'^(\d+)/$', views.question, name='question'), +] diff --git a/samples/development-frameworks/django/bootcamp/questions/views.py b/samples/development-frameworks/django/bootcamp/questions/views.py new file mode 100644 index 00000000..95b18e21 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/questions/views.py @@ -0,0 +1,164 @@ +from django.shortcuts import render, redirect, get_object_or_404 +from django.http import HttpResponse, HttpResponseForbidden +from bootcamp.questions.models import Question, Answer +from bootcamp.questions.forms import QuestionForm, AnswerForm +from bootcamp.activities.models import Activity +from django.db.models import Q +from django.contrib.auth.decorators import login_required +from bootcamp.decorators import ajax_required +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger + + +@login_required +def _questions(request, questions, active): + paginator = Paginator(questions, 10) + page = request.GET.get('page') + try: + questions = paginator.page(page) + except PageNotAnInteger: + questions = paginator.page(1) + except EmptyPage: + questions = paginator.page(paginator.num_pages) + return render(request, 'questions/questions.html', { + 'questions': questions, + 'active': active + }) + + +@login_required +def questions(request): + return unanswered(request) + + +@login_required +def answered(request): + questions = Question.get_answered() + return _questions(request, questions, 'answered') + + +@login_required +def unanswered(request): + questions = Question.get_unanswered() + return _questions(request, questions, 'unanswered') + + +@login_required +def all(request): + questions = Question.objects.all() + return _questions(request, questions, 'all') + + +@login_required +def ask(request): + if request.method == 'POST': + form = QuestionForm(request.POST) + if form.is_valid(): + question = Question() + question.user = request.user + question.title = form.cleaned_data.get('title') + question.description = form.cleaned_data.get('description') + question.save() + tags = form.cleaned_data.get('tags') + question.create_tags(tags) + return redirect('/questions/') + + else: + return render(request, 'questions/ask.html', {'form': form}) + + else: + form = QuestionForm() + + return render(request, 'questions/ask.html', {'form': form}) + + +@login_required +def question(request, pk): + question = get_object_or_404(Question, pk=pk) + form = AnswerForm(initial={'question': question}) + return render(request, 'questions/question.html', { + 'question': question, + 'form': form + }) + + +@login_required +def answer(request): + if request.method == 'POST': + form = AnswerForm(request.POST) + if form.is_valid(): + user = request.user + answer = Answer() + answer.user = request.user + answer.question = form.cleaned_data.get('question') + answer.description = form.cleaned_data.get('description') + answer.save() + user.profile.notify_answered(answer.question) + return redirect(u'/questions/{0}/'.format(answer.question.pk)) + else: + question = form.cleaned_data.get('question') + return render(request, 'questions/question.html', { + 'question': question, + 'form': form + }) + else: + return redirect('/questions/') + + +@login_required +@ajax_required +def accept(request): + answer_id = request.POST['answer'] + answer = Answer.objects.get(pk=answer_id) + user = request.user + try: + # answer.accept cleans previous accepted answer + user.profile.unotify_accepted(answer.question.get_accepted_answer()) + + except Exception, e: + pass + + if answer.question.user == user: + answer.accept() + user.profile.notify_accepted(answer) + return HttpResponse() + + else: + return HttpResponseForbidden() + + +@login_required +@ajax_required +def vote(request): + answer_id = request.POST['answer'] + answer = Answer.objects.get(pk=answer_id) + vote = request.POST['vote'] + user = request.user + activity = Activity.objects.filter( + Q(activity_type=Activity.UP_VOTE) | Q(activity_type=Activity.DOWN_VOTE), + user=user, answer=answer_id) + if activity: + activity.delete() + if vote in [Activity.UP_VOTE, Activity.DOWN_VOTE]: + activity = Activity(activity_type=vote, user=user, answer=answer_id) + activity.save() + return HttpResponse(answer.calculate_votes()) + + +@login_required +@ajax_required +def favorite(request): + question_id = request.POST['question'] + question = Question.objects.get(pk=question_id) + user = request.user + activity = Activity.objects.filter(activity_type=Activity.FAVORITE, + user=user, question=question_id) + if activity: + activity.delete() + user.profile.unotify_favorited(question) + else: + activity = Activity(activity_type=Activity.FAVORITE, user=user, + question=question_id) + activity.save() + user.profile.notify_favorited(question) + + return HttpResponse(question.calculate_favorites()) diff --git a/samples/development-frameworks/django/bootcamp/search/__init__.py b/samples/development-frameworks/django/bootcamp/search/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/search/migrations/__init__.py b/samples/development-frameworks/django/bootcamp/search/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/samples/development-frameworks/django/bootcamp/search/models.py b/samples/development-frameworks/django/bootcamp/search/models.py new file mode 100644 index 00000000..0f022274 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/search/models.py @@ -0,0 +1 @@ +from django.db import models \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/search/static/css/search.css b/samples/development-frameworks/django/bootcamp/search/static/css/search.css new file mode 100644 index 00000000..8800ee25 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/search/static/css/search.css @@ -0,0 +1,91 @@ +.results ul { + padding: 0; +} + +.results ul li { + list-style: none; + padding: .8em .4em; + border-bottom: 1px solid #eeeeee; +} + +.results ul li:last-child { + border-bottom: none; +} + +.results ul li img { + width: 45px; + border-radius: 5px; +} + +.feed-results h3 { + margin: 0; + font-size: 1.2em; +} + +.feed-results .post { + margin-left: 55px; + padding-top: .2em; +} + +.feed-results .post p { + margin: 0; + margin-top: .2em; +} + +[class$='-results'] li:hover { + cursor: pointer; + background-color: #f5f8fa; +} + +.info { + margin-bottom: .5em; + color: #a0a0a0; +} + +.info a { + color: #a0a0a0; +} + +.info > span { + margin-right: 1em; +} + +.info .user img { + width: 20px; + border-radius: 3px; +} + +.no-result { + margin-top: 2em; +} + +.result-user { + width: 20px; + border-radius: 4px; + margin-right: .2em; +} + +.results { + margin-top: 1em; +} + +.article-content ul, +.article-content ol, +.question-description ul, +.question-description ol { + padding-left: 20px; +} + +.article-content ul li, +.question-description ul li { + border-bottom: none; + list-style: disc; + padding: 0; +} + +.article-content ol li, +.question-description ol li { + border-bottom: none; + list-style: decimal; + padding: 0; +} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/search/static/js/search.js b/samples/development-frameworks/django/bootcamp/search/static/js/search.js new file mode 100644 index 00000000..8f7a5ab4 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/search/static/js/search.js @@ -0,0 +1,17 @@ +$(function () { + $(".feed-results li").click(function () { + var feed = $(this).attr("feed-id"); + location.href = "/feeds/" + feed + "/"; + }); + + $(".articles-results li").click(function () { + var article = $(this).attr("article-slug"); + location.href = "/articles/" + article + "/"; + }); + + $(".questions-results li").click(function () { + var question = $(this).attr("question-id"); + location.href = "/questions/" + question + "/"; + }); + +}); \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/search/templates/search/partial_articles_results.html b/samples/development-frameworks/django/bootcamp/search/templates/search/partial_articles_results.html new file mode 100644 index 00000000..5ee50b14 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/search/templates/search/partial_articles_results.html @@ -0,0 +1,25 @@ +{% load i18n %} + +

    {% trans 'Articles' %}

    +{% if results %} + +{% else %} +

    {% trans 'No article found' %} :(

    +{% endif %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/search/templates/search/partial_feed_results.html b/samples/development-frameworks/django/bootcamp/search/templates/search/partial_feed_results.html new file mode 100644 index 00000000..c29c7bad --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/search/templates/search/partial_feed_results.html @@ -0,0 +1,24 @@ +{% load i18n %} +{% load humanize %} + +

    {% trans 'Feed' %}

    +{% if results %} + +{% else %} +

    {% trans 'No feed found' %} :(

    +{% endif %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/search/templates/search/partial_questions_results.html b/samples/development-frameworks/django/bootcamp/search/templates/search/partial_questions_results.html new file mode 100644 index 00000000..72619618 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/search/templates/search/partial_questions_results.html @@ -0,0 +1,23 @@ +{% load i18n %} +{% load humanize %} + +

    {% trans 'Questions' %}

    +{% if results %} + +{% else %} +

    {% trans 'No question found' %} :(

    +{% endif %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/search/templates/search/partial_results_menu.html b/samples/development-frameworks/django/bootcamp/search/templates/search/partial_results_menu.html new file mode 100644 index 00000000..5bcb56a7 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/search/templates/search/partial_results_menu.html @@ -0,0 +1,20 @@ +{% load i18n %} + + \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/search/templates/search/partial_users_results.html b/samples/development-frameworks/django/bootcamp/search/templates/search/partial_users_results.html new file mode 100644 index 00000000..4a732b88 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/search/templates/search/partial_users_results.html @@ -0,0 +1,20 @@ +{% load i18n %} + +

    {% trans 'Users' %}

    +{% if results %} + +{% else %} +

    {% trans 'No user found' %} :(

    +{% endif %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/search/templates/search/results.html b/samples/development-frameworks/django/bootcamp/search/templates/search/results.html new file mode 100644 index 00000000..d0daa53d --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/search/templates/search/results.html @@ -0,0 +1,48 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} +{% load humanize %} + +{% block title %} Search {% endblock %} + +{% block head %} + + +{% endblock head %} + +{% block main %} + +
    +
    + {% include 'search/partial_results_menu.html' with active=active count=count querystring=querystring %} +
    +
    + {% if active == 'feed' %} + {% include 'search/partial_feed_results.html' with results=results %} + {% elif active == 'articles' %} + {% include 'search/partial_articles_results.html' with results=results %} + {% elif active == 'questions' %} + {% include 'search/partial_questions_results.html' with results=results %} + {% elif active == 'users' %} + {% include 'search/partial_users_results.html' with results=results %} + {% endif %} +
    +
    +{% endblock main %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/search/templates/search/search.html b/samples/development-frameworks/django/bootcamp/search/templates/search/search.html new file mode 100644 index 00000000..9d5415a8 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/search/templates/search/search.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load i18n %} +{% load humanize %} + +{% block title %} Search {% endblock %} + +{% block head %} + +{% endblock head %} + +{% block main %} + + +
    +
    +

    {% trans "Search Feed, Articles, Questions and Users" %}

    +
    +
    + + + + +
    +
    +
    +
    +{% endblock main %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/search/tests.py b/samples/development-frameworks/django/bootcamp/search/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/search/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/samples/development-frameworks/django/bootcamp/search/views.py b/samples/development-frameworks/django/bootcamp/search/views.py new file mode 100644 index 00000000..aa10e615 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/search/views.py @@ -0,0 +1,52 @@ +from django.shortcuts import render, redirect +from django.db.models import Q +from django.contrib.auth.models import User +from bootcamp.feeds.models import Feed +from bootcamp.articles.models import Article +from bootcamp.questions.models import Question +from django.contrib.auth.decorators import login_required + + +@login_required +def search(request): + if 'q' in request.GET: + querystring = request.GET.get('q').strip() + if len(querystring) == 0: + return redirect('/search/') + + try: + search_type = request.GET.get('type') + if search_type not in ['feed', 'articles', 'questions', 'users']: + search_type = 'feed' + + except Exception, e: + search_type = 'feed' + + count = {} + results = {} + results['feed'] = Feed.objects.filter(post__icontains=querystring, + parent=None) + results['articles'] = Article.objects.filter( + Q(title__icontains=querystring) | Q(content__icontains=querystring) + ) + results['questions'] = Question.objects.filter( + Q(title__icontains=querystring) | Q( + description__icontains=querystring)) + results['users'] = User.objects.filter( + Q(username__icontains=querystring) | Q( + first_name__icontains=querystring) | Q( + last_name__icontains=querystring)) + count['feed'] = results['feed'].count() + count['articles'] = results['articles'].count() + count['questions'] = results['questions'].count() + count['users'] = results['users'].count() + + return render(request, 'search/results.html', { + 'hide_search': True, + 'querystring': querystring, + 'active': search_type, + 'count': count, + 'results': results[search_type], + }) + else: + return render(request, 'search/search.html', {'hide_search': True}) diff --git a/samples/development-frameworks/django/bootcamp/settings.py b/samples/development-frameworks/django/bootcamp/settings.py new file mode 100644 index 00000000..f412c7a0 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/settings.py @@ -0,0 +1,104 @@ +from unipath import Path +from decouple import config +import dj_database_url + + +PROJECT_DIR = Path(__file__).parent + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ + +SECRET_KEY = config('SECRET_KEY') + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = config('DEBUG', default=False, cast=bool) +TEMPLATE_DEBUG = DEBUG + +DATABASES = { + 'default': dj_database_url.config(default=config('DATABASE_URL')) +} + +ALLOWED_HOSTS = ['*'] + +# Application definition + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'django.contrib.humanize', + + 'bootcamp.activities', + 'bootcamp.articles', + 'bootcamp.authentication', + 'bootcamp.core', + 'bootcamp.feeds', + 'bootcamp.messenger', + 'bootcamp.questions', + 'bootcamp.search', +) + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.middleware.locale.LocaleMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) + +ROOT_URLCONF = 'bootcamp.urls' + +WSGI_APPLICATION = 'bootcamp.wsgi.application' + +# Internationalization +# https://docs.djangoproject.com/en/1.6/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +LANGUAGES = ( + ('en', 'English'), + ('pt-br', 'Portuguese'), + ('es', 'Spanish') +) + +LOCALE_PATHS = (PROJECT_DIR.child('locale'), ) + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.6/howto/static-files/ + +STATIC_ROOT = PROJECT_DIR.parent.child('staticfiles') +STATIC_URL = '/static/' + +STATICFILES_DIRS = ( + PROJECT_DIR.child('static'), +) + +STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage' + + +MEDIA_ROOT = PROJECT_DIR.parent.child('media') +MEDIA_URL = '/media/' + +TEMPLATE_DIRS = ( + PROJECT_DIR.child('templates'), +) + +LOGIN_URL = '/' +LOGIN_REDIRECT_URL = '/feeds/' + +ALLOWED_SIGNUP_DOMAINS = ['*'] + +FILE_UPLOAD_TEMP_DIR = '/tmp/' +FILE_UPLOAD_PERMISSIONS = 0o644 diff --git a/samples/development-frameworks/django/bootcamp/static/css/bootcamp.css b/samples/development-frameworks/django/bootcamp/static/css/bootcamp.css new file mode 100644 index 00000000..eda6b450 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/static/css/bootcamp.css @@ -0,0 +1,76 @@ +@import url(https://fonts.googleapis.com/css?family=Audiowide); + +body { + padding-top: 70px; +} + +header .navbar-brand { + font-size: 1.4em; + font-weight: 200; + font-family: "Audiowide", cursive; +} + +main .container { + +} + +.page-header { + margin: 0; +} + +.page-header h1 { + margin: 0; + font-weight: 100; + font-size: 2em; +} + +.no-data { + text-align: center; + padding: 1em 0; +} + +#notifications { + font-size: 1.5em; + padding: 13px; + color: #dddddd; +} + +#notifications.new-notifications { + color: #428bca; +} + +.popover { + max-width: 350px; + width: 350px; +} + +.popover ul { + padding: 0; + margin: 0; +} + +.popover ul li { + list-style: none; + border-bottom: 1px solid #eeeeee; + padding: .4em 0; +} + +.popover ul li:last-child { + border-bottom: none; +} + +.popover ul li .user-picture { + width: 45px; + float: left; +} + +.popover ul li p { + font-size: .9em; + padding: 0 0 0 .6em; + margin-left: 45px; + margin-bottom: 0; +} + +.markdown { + margin-bottom: .8em; +} diff --git a/samples/development-frameworks/django/bootcamp/static/css/jquery.Jcrop.min.css b/samples/development-frameworks/django/bootcamp/static/css/jquery.Jcrop.min.css new file mode 100644 index 00000000..ec9c5d66 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/static/css/jquery.Jcrop.min.css @@ -0,0 +1,30 @@ +/* jquery.Jcrop.min.css v0.9.12 (build:20130126) */ +.jcrop-holder{direction:ltr;text-align:left;} +.jcrop-vline,.jcrop-hline{background:#FFF url(../img/Jcrop.gif);font-size:0;position:absolute;} +.jcrop-vline{height:100%;width:1px!important;} +.jcrop-vline.right{right:0;} +.jcrop-hline{height:1px!important;width:100%;} +.jcrop-hline.bottom{bottom:0;} +.jcrop-tracker{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-user-select:none;height:100%;width:100%;} +.jcrop-handle{background-color:#333;border:1px #EEE solid;font-size:1px;height:7px;width:7px;} +.jcrop-handle.ord-n{left:50%;margin-left:-4px;margin-top:-4px;top:0;} +.jcrop-handle.ord-s{bottom:0;left:50%;margin-bottom:-4px;margin-left:-4px;} +.jcrop-handle.ord-e{margin-right:-4px;margin-top:-4px;right:0;top:50%;} +.jcrop-handle.ord-w{left:0;margin-left:-4px;margin-top:-4px;top:50%;} +.jcrop-handle.ord-nw{left:0;margin-left:-4px;margin-top:-4px;top:0;} +.jcrop-handle.ord-ne{margin-right:-4px;margin-top:-4px;right:0;top:0;} +.jcrop-handle.ord-se{bottom:0;margin-bottom:-4px;margin-right:-4px;right:0;} +.jcrop-handle.ord-sw{bottom:0;left:0;margin-bottom:-4px;margin-left:-4px;} +.jcrop-dragbar.ord-n,.jcrop-dragbar.ord-s{height:7px;width:100%;} +.jcrop-dragbar.ord-e,.jcrop-dragbar.ord-w{height:100%;width:7px;} +.jcrop-dragbar.ord-n{margin-top:-4px;} +.jcrop-dragbar.ord-s{bottom:0;margin-bottom:-4px;} +.jcrop-dragbar.ord-e{margin-right:-4px;right:0;} +.jcrop-dragbar.ord-w{margin-left:-4px;} +.jcrop-light .jcrop-vline,.jcrop-light .jcrop-hline{background:#FFF;filter:alpha(opacity=70)!important;opacity:.70!important;} +.jcrop-light .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#000;border-color:#FFF;border-radius:3px;} +.jcrop-dark .jcrop-vline,.jcrop-dark .jcrop-hline{background:#000;filter:alpha(opacity=70)!important;opacity:.7!important;} +.jcrop-dark .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#FFF;border-color:#000;border-radius:3px;} +.solid-line .jcrop-vline,.solid-line .jcrop-hline{background:#FFF;} +.jcrop-holder img,img.jcrop-preview{max-width:none;} +.jcrop-keymgr{display:none!important;} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/static/img/Jcrop.gif b/samples/development-frameworks/django/bootcamp/static/img/Jcrop.gif new file mode 100644 index 0000000000000000000000000000000000000000..72ea7ccb5321d5384d70437cfaac73011237901e GIT binary patch literal 329 zcmZ?wbhEHb9b#5NV>2k zBC~b@b~P=nNfWAe-b%_i6tS^-1y(h@EsB~1TqDA_h@fkxG$bHgvj}VxE1JLgr!*!^ ILUxTc0Q$^Q5C8xG literal 0 HcmV?d00001 diff --git a/samples/development-frameworks/django/bootcamp/static/img/favicon.png b/samples/development-frameworks/django/bootcamp/static/img/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1537675f2deecc634d260bb2e30137d60ba891bf GIT binary patch literal 4256 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 literal 0 HcmV?d00001 diff --git a/samples/development-frameworks/django/bootcamp/static/img/loading.gif b/samples/development-frameworks/django/bootcamp/static/img/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..ec59bde7d264a9e293785b9d67af4371319be6d0 GIT binary patch literal 11598 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 literal 0 HcmV?d00001 diff --git a/samples/development-frameworks/django/bootcamp/static/img/user.png b/samples/development-frameworks/django/bootcamp/static/img/user.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d631ec0f0bde54118e1f823061dbd5c2357433 GIT binary patch literal 4693 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 0) { + selection_list_result = selection_list_result.substring(0, selection_list_result.length - 1); + } + $(_textarea).selection("replace", {text: selection_list_result}); + break; + case "link": + $("#markdown_link_text").val(""); + $("#markdown_url").val(""); + $("#markdown_insert_link").modal("show"); + break; + case "picture": + $("#markdown_picture_url").val(""); + $("#markdown_alt_text").val(""); + $("#markdown_insert_picture").modal("show"); + break; + case "indent-left": + var selection_list = selection.split("\n"); + var selection_list_result = ""; + for (var i = 0; i < selection_list.length; i++) { + selection_list_result += " " + selection_list[i] + "\n"; + }; + if (selection_list_result.length > 0) { + selection_list_result = selection_list_result.substring(0, selection_list_result.length - 1); + } + $(_textarea).selection("replace", {text: selection_list_result}); + break; + case "indent-right": + var selection_list = selection.split("\n"); + var selection_list_result = ""; + for (var i = 0; i < selection_list.length ; i++) { + selection_list_result += selection_list[i].trim() + "\n"; + }; + if (selection_list_result.length > 0) { + selection_list_result = selection_list_result.substring(0, selection_list_result.length - 1); + } + $(_textarea).selection("replace", {text: selection_list_result}); + break; + }; + + }); + + $(".add_link").click(function () { + var selection = $(_textarea).selection(); + var text = $("#markdown_link_text").val(); + var url = $("#markdown_url").val(); + var link = "[" + text + "](" + url + ")"; + $(_textarea).selection("replace", {text: link}); + $("#markdown_insert_link").modal("hide") + }); + + $(".add_picture").click(function () { + var selection = $(_textarea).selection(); + var url = $("#markdown_picture_url").val(); + var alt = $("#markdown_alt_text").val(); + var picture = "![" + alt + "](" + url + ")"; + $(_textarea).selection("replace", {text: picture}); + $("#markdown_insert_picture").modal("hide") + }); + + }; +}); \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/static/js/ga.js b/samples/development-frameworks/django/bootcamp/static/js/ga.js new file mode 100644 index 00000000..e2064f4c --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/static/js/ga.js @@ -0,0 +1,7 @@ + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + + ga('create', 'UA-42049022-5', 'vitorfs.com'); + ga('send', 'pageview'); \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/static/js/jquery.Jcrop.min.js b/samples/development-frameworks/django/bootcamp/static/js/jquery.Jcrop.min.js new file mode 100644 index 00000000..4c9c7adb --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/static/js/jquery.Jcrop.min.js @@ -0,0 +1,22 @@ +/** + * jquery.Jcrop.min.js v0.9.12 (build:20130202) + * jQuery Image Cropping Plugin - released under MIT License + * Copyright (c) 2008-2013 Tapmodo Interactive LLC + * https://github.com/tapmodo/Jcrop + */ +(function(a){a.Jcrop=function(b,c){function i(a){return Math.round(a)+"px"}function j(a){return d.baseClass+"-"+a}function k(){return a.fx.step.hasOwnProperty("backgroundColor")}function l(b){var c=a(b).offset();return[c.left,c.top]}function m(a){return[a.pageX-e[0],a.pageY-e[1]]}function n(b){typeof b!="object"&&(b={}),d=a.extend(d,b),a.each(["onChange","onSelect","onRelease","onDblClick"],function(a,b){typeof d[b]!="function"&&(d[b]=function(){})})}function o(a,b,c){e=l(D),bc.setCursor(a==="move"?a:a+"-resize");if(a==="move")return bc.activateHandlers(q(b),v,c);var d=_.getFixed(),f=r(a),g=_.getCorner(r(f));_.setPressed(_.getCorner(f)),_.setCurrent(g),bc.activateHandlers(p(a,d),v,c)}function p(a,b){return function(c){if(!d.aspectRatio)switch(a){case"e":c[1]=b.y2;break;case"w":c[1]=b.y2;break;case"n":c[0]=b.x2;break;case"s":c[0]=b.x2}else switch(a){case"e":c[1]=b.y+1;break;case"w":c[1]=b.y+1;break;case"n":c[0]=b.x+1;break;case"s":c[0]=b.x+1}_.setCurrent(c),bb.update()}}function q(a){var b=a;return bd.watchKeys +(),function(a){_.moveOffset([a[0]-b[0],a[1]-b[1]]),b=a,bb.update()}}function r(a){switch(a){case"n":return"sw";case"s":return"nw";case"e":return"nw";case"w":return"ne";case"ne":return"sw";case"nw":return"se";case"se":return"nw";case"sw":return"ne"}}function s(a){return function(b){return d.disabled?!1:a==="move"&&!d.allowMove?!1:(e=l(D),W=!0,o(a,m(b)),b.stopPropagation(),b.preventDefault(),!1)}}function t(a,b,c){var d=a.width(),e=a.height();d>b&&b>0&&(d=b,e=b/a.width()*a.height()),e>c&&c>0&&(e=c,d=c/a.height()*a.width()),T=a.width()/d,U=a.height()/e,a.width(d).height(e)}function u(a){return{x:a.x*T,y:a.y*U,x2:a.x2*T,y2:a.y2*U,w:a.w*T,h:a.h*U}}function v(a){var b=_.getFixed();b.w>d.minSelect[0]&&b.h>d.minSelect[1]?(bb.enableHandles(),bb.done()):bb.release(),bc.setCursor(d.allowSelect?"crosshair":"default")}function w(a){if(d.disabled)return!1;if(!d.allowSelect)return!1;W=!0,e=l(D),bb.disableHandles(),bc.setCursor("crosshair");var b=m(a);return _.setPressed(b),bb.update(),bc.activateHandlers(x,v,a.type.substring +(0,5)==="touch"),bd.watchKeys(),a.stopPropagation(),a.preventDefault(),!1}function x(a){_.setCurrent(a),bb.update()}function y(){var b=a("
    ").addClass(j("tracker"));return g&&b.css({opacity:0,backgroundColor:"white"}),b}function be(a){G.removeClass().addClass(j("holder")).addClass(a)}function bf(a,b){function t(){window.setTimeout(u,l)}var c=a[0]/T,e=a[1]/U,f=a[2]/T,g=a[3]/U;if(X)return;var h=_.flipCoords(c,e,f,g),i=_.getFixed(),j=[i.x,i.y,i.x2,i.y2],k=j,l=d.animationDelay,m=h[0]-j[0],n=h[1]-j[1],o=h[2]-j[2],p=h[3]-j[3],q=0,r=d.swingSpeed;c=k[0],e=k[1],f=k[2],g=k[3],bb.animMode(!0);var s,u=function(){return function(){q+=(100-q)/r,k[0]=Math.round(c+q/100*m),k[1]=Math.round(e+q/100*n),k[2]=Math.round(f+q/100*o),k[3]=Math.round(g+q/100*p),q>=99.8&&(q=100),q<100?(bh(k),t()):(bb.done(),bb.animMode(!1),typeof b=="function"&&b.call(bs))}}();t()}function bg(a){bh([a[0]/T,a[1]/U,a[2]/T,a[3]/U]),d.onSelect.call(bs,u(_.getFixed())),bb.enableHandles()}function bh(a){_.setPressed([a[0],a[1]]),_.setCurrent([a[2], +a[3]]),bb.update()}function bi(){return u(_.getFixed())}function bj(){return _.getFixed()}function bk(a){n(a),br()}function bl(){d.disabled=!0,bb.disableHandles(),bb.setCursor("default"),bc.setCursor("default")}function bm(){d.disabled=!1,br()}function bn(){bb.done(),bc.activateHandlers(null,null)}function bo(){G.remove(),A.show(),A.css("visibility","visible"),a(b).removeData("Jcrop")}function bp(a,b){bb.release(),bl();var c=new Image;c.onload=function(){var e=c.width,f=c.height,g=d.boxWidth,h=d.boxHeight;D.width(e).height(f),D.attr("src",a),H.attr("src",a),t(D,g,h),E=D.width(),F=D.height(),H.width(E).height(F),M.width(E+L*2).height(F+L*2),G.width(E).height(F),ba.resize(E,F),bm(),typeof b=="function"&&b.call(bs)},c.src=a}function bq(a,b,c){var e=b||d.bgColor;d.bgFade&&k()&&d.fadeTime&&!c?a.animate({backgroundColor:e},{queue:!1,duration:d.fadeTime}):a.css("backgroundColor",e)}function br(a){d.allowResize?a?bb.enableOnly():bb.enableHandles():bb.disableHandles(),bc.setCursor(d.allowSelect?"crosshair":"default"),bb +.setCursor(d.allowMove?"move":"default"),d.hasOwnProperty("trueSize")&&(T=d.trueSize[0]/E,U=d.trueSize[1]/F),d.hasOwnProperty("setSelect")&&(bg(d.setSelect),bb.done(),delete d.setSelect),ba.refresh(),d.bgColor!=N&&(bq(d.shade?ba.getShades():G,d.shade?d.shadeColor||d.bgColor:d.bgColor),N=d.bgColor),O!=d.bgOpacity&&(O=d.bgOpacity,d.shade?ba.refresh():bb.setBgOpacity(O)),P=d.maxSize[0]||0,Q=d.maxSize[1]||0,R=d.minSize[0]||0,S=d.minSize[1]||0,d.hasOwnProperty("outerImage")&&(D.attr("src",d.outerImage),delete d.outerImage),bb.refresh()}var d=a.extend({},a.Jcrop.defaults),e,f=navigator.userAgent.toLowerCase(),g=/msie/.test(f),h=/msie [1-6]\./.test(f);typeof b!="object"&&(b=a(b)[0]),typeof c!="object"&&(c={}),n(c);var z={border:"none",visibility:"visible",margin:0,padding:0,position:"absolute",top:0,left:0},A=a(b),B=!0;if(b.tagName=="IMG"){if(A[0].width!=0&&A[0].height!=0)A.width(A[0].width),A.height(A[0].height);else{var C=new Image;C.src=A[0].src,A.width(C.width),A.height(C.height)}var D=A.clone().removeAttr("id"). +css(z).show();D.width(A.width()),D.height(A.height()),A.after(D).hide()}else D=A.css(z).show(),B=!1,d.shade===null&&(d.shade=!0);t(D,d.boxWidth,d.boxHeight);var E=D.width(),F=D.height(),G=a("
    ").width(E).height(F).addClass(j("holder")).css({position:"relative",backgroundColor:d.bgColor}).insertAfter(A).append(D);d.addClass&&G.addClass(d.addClass);var H=a("
    "),I=a("
    ").width("100%").height("100%").css({zIndex:310,position:"absolute",overflow:"hidden"}),J=a("
    ").width("100%").height("100%").css("zIndex",320),K=a("
    ").css({position:"absolute",zIndex:600}).dblclick(function(){var a=_.getFixed();d.onDblClick.call(bs,a)}).insertBefore(D).append(I,J);B&&(H=a("").attr("src",D.attr("src")).css(z).width(E).height(F),I.append(H)),h&&K.css({overflowY:"hidden"});var L=d.boundary,M=y().width(E+L*2).height(F+L*2).css({position:"absolute",top:i(-L),left:i(-L),zIndex:290}).mousedown(w),N=d.bgColor,O=d.bgOpacity,P,Q,R,S,T,U,V=!0,W,X,Y;e=l(D);var Z=function(){function a(){var a={},b=["touchstart" +,"touchmove","touchend"],c=document.createElement("div"),d;try{for(d=0;da+f&&(f-=f+a),0>b+g&&(g-=g+b),FE&&(r=E,u=Math.abs((r-a)/f),s=k<0?b-u:u+b)):(r=c,u=l/f,s=k<0?b-u:b+u,s<0?(s=0,t=Math.abs((s-b)*f),r=j<0?a-t:t+a):s>F&&(s=F,t=Math.abs(s-b)*f,r=j<0?a-t:t+a)),r>a?(r-ah&&(r=a+h),s>b?s=b+(r-a)/f:s=b-(r-a)/f):rh&&(r=a-h),s>b?s=b+(a-r)/f:s=b-(a-r)/f),r<0?(a-=r,r=0):r>E&&(a-=r-E,r=E),s<0?(b-=s,s=0):s>F&&(b-=s-F,s=F),q(o(a,b,r,s))}function n(a){return a[0]<0&&(a[0]=0),a[1]<0&&(a[1]=0),a[0]>E&&(a[0]=E),a[1]>F&&(a[1]=F),[Math.round(a[0]),Math.round(a[1])]}function o(a,b,c,d){var e=a,f=c,g=b,h=d;return cP&&(c=d>0?a+P:a-P),Q&&Math.abs +(f)>Q&&(e=f>0?b+Q:b-Q),S/U&&Math.abs(f)0?b+S/U:b-S/U),R/T&&Math.abs(d)0?a+R/T:a-R/T),a<0&&(c-=a,a-=a),b<0&&(e-=b,b-=b),c<0&&(a-=c,c-=c),e<0&&(b-=e,e-=e),c>E&&(g=c-E,a-=g,c-=g),e>F&&(g=e-F,b-=g,e-=g),a>E&&(g=a-F,e-=g,b-=g),b>F&&(g=b-F,e-=g,b-=g),q(o(a,b,c,e))}function q(a){return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]}}var a=0,b=0,c=0,e=0,f,g;return{flipCoords:o,setPressed:h,setCurrent:i,getOffset:j,moveOffset:k,getCorner:l,getFixed:m}}(),ba=function(){function f(a,b){e.left.css({height:i(b)}),e.right.css({height:i(b)})}function g(){return h(_.getFixed())}function h(a){e.top.css({left:i(a.x),width:i(a.w),height:i(a.y)}),e.bottom.css({top:i(a.y2),left:i(a.x),width:i(a.w),height:i(F-a.y2)}),e.right.css({left:i(a.x2),width:i(E-a.x2)}),e.left.css({width:i(a.x)})}function j(){return a("
    ").css({position:"absolute",backgroundColor:d.shadeColor||d.bgColor}).appendTo(c)}function k(){b||(b=!0,c.insertBefore(D),g(),bb.setBgOpacity(1,0,1),H.hide(),l(d.shadeColor||d.bgColor,1),bb. +isAwake()?n(d.bgOpacity,1):n(1,1))}function l(a,b){bq(p(),a,b)}function m(){b&&(c.remove(),H.show(),b=!1,bb.isAwake()?bb.setBgOpacity(d.bgOpacity,1,1):(bb.setBgOpacity(1,1,1),bb.disableHandles()),bq(G,0,1))}function n(a,e){b&&(d.bgFade&&!e?c.animate({opacity:1-a},{queue:!1,duration:d.fadeTime}):c.css({opacity:1-a}))}function o(){d.shade?k():m(),bb.isAwake()&&n(d.bgOpacity)}function p(){return c.children()}var b=!1,c=a("
    ").css({position:"absolute",zIndex:240,opacity:0}),e={top:j(),left:j().height(F),right:j().height(F),bottom:j()};return{update:g,updateRaw:h,getShades:p,setBgColor:l,enable:k,disable:m,resize:f,refresh:o,opacity:n}}(),bb=function(){function k(b){var c=a("
    ").css({position:"absolute",opacity:d.borderOpacity}).addClass(j(b));return I.append(c),c}function l(b,c){var d=a("
    ").mousedown(s(b)).css({cursor:b+"-resize",position:"absolute",zIndex:c}).addClass("ord-"+b);return Z.support&&d.bind("touchstart.jcrop",Z.createDragger(b)),J.append(d),d}function m(a){var b=d.handleSize,e=l(a,c++ +).css({opacity:d.handleOpacity}).addClass(j("handle"));return b&&e.width(b).height(b),e}function n(a){return l(a,c++).addClass("jcrop-dragbar")}function o(a){var b;for(b=0;b').css({position:"fixed",left:"-120px",width:"12px"}).addClass("jcrop-keymgr"),c=a("
    ").css({position:"absolute",overflow:"hidden"}).append(b);return d.keySupport&&(b.keydown(i).blur(f),h||!d.fixedSupport?(b.css({position:"absolute",left:"-20px"}),c.append(b).insertBefore(D)):b.insertBefore(D)),{watchKeys:e}}();Z.support&&M.bind("touchstart.jcrop",Z.newSelection),J.hide(),br(!0);var bs={setImage:bp,animateTo:bf,setSelect:bg,setOptions:bk,tellSelect:bi,tellScaled:bj,setClass:be,disable:bl,enable:bm,cancel:bn,release:bb.release,destroy:bo,focus:bd.watchKeys,getBounds:function(){return[E*T,F*U]},getWidgetSize:function(){return[E,F]},getScaleFactor:function(){return[T,U]},getOptions:function(){return d},ui:{holder:G,selection:K}};return g&&G.bind("selectstart",function(){return!1}),A.data("Jcrop",bs),bs},a.fn.Jcrop=function(b,c){var d;return this.each(function(){if(a(this).data("Jcrop")){if( +b==="api")return a(this).data("Jcrop");a(this).data("Jcrop").setOptions(b)}else this.tagName=="IMG"?a.Jcrop.Loader(this,function(){a(this).css({display:"block",visibility:"hidden"}),d=a.Jcrop(this,b),a.isFunction(c)&&c.call(d)}):(a(this).css({display:"block",visibility:"hidden"}),d=a.Jcrop(this,b),a.isFunction(c)&&c.call(d))}),this},a.Jcrop.Loader=function(b,c,d){function g(){f.complete?(e.unbind(".jcloader"),a.isFunction(c)&&c.call(f)):window.setTimeout(g,50)}var e=a(b),f=e[0];e.bind("load.jcloader",g).bind("error.jcloader",function(b){e.unbind(".jcloader"),a.isFunction(d)&&d.call(f)}),f.complete&&a.isFunction(c)&&(e.unbind(".jcloader"),c.call(f))},a.Jcrop.defaults={allowSelect:!0,allowMove:!0,allowResize:!0,trackDocument:!0,baseClass:"jcrop",addClass:null,bgColor:"black",bgOpacity:.6,bgFade:!1,borderOpacity:.4,handleOpacity:.5,handleSize:null,aspectRatio:0,keySupport:!0,createHandles:["n","s","e","w","nw","ne","se","sw"],createDragbars:["n","s","e","w"],createBorders:["n","s","e","w"],drawBorders:!0,dragEdges +:!0,fixedSupport:!0,touchSupport:null,shade:null,boxWidth:0,boxHeight:0,boundary:2,fadeTime:400,animationDelay:20,swingSpeed:3,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){},onDblClick:function(){},onRelease:function(){}}})(jQuery); \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/static/js/jquery.bullseye-1.0-min.js b/samples/development-frameworks/django/bootcamp/static/js/jquery.bullseye-1.0-min.js new file mode 100644 index 00000000..22f7e2c8 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/static/js/jquery.bullseye-1.0-min.js @@ -0,0 +1,10 @@ +/*! +* jQuery Bullseye v1.0 +* http://pixeltango.com +* +* Copyright 2010, Mickel Andersson +* Dual licensed under the MIT or GPL Version 2 licenses. +* +* Date: Fri Aug 31 19:09:11 2010 +0100 +*/ +jQuery.fn.bullseye = function (b, h) { b = jQuery.extend({ offsetTop: 0, offsetHeight: 0, extendDown: false }, b); return this.each(function () { var a = $(this), c = $(h == null ? window : h), g = function () { var d = a.outerWidth(), e = a.outerHeight() + b.offsetHeight; c.width(); var f = c.height(), i = c.scrollTop(), j = c.scrollLeft() + d; f = i + f; var k = a.offset().left; d = k + d; var l = a.offset().top + b.offsetTop; e = l + e; if (f < l || (b.extendDown ? false : i > e) || j < k || j > d) { if (a.data("is-focused")) { a.data("is-focused", false); a.trigger("leaveviewport") } } else if (!a.data("is-focused")) { a.data("is-focused", true); a.trigger("enterviewport") } }; c.scroll(g).resize(g); g() }) }; \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/static/js/jquery.selection.js b/samples/development-frameworks/django/bootcamp/static/js/jquery.selection.js new file mode 100644 index 00000000..5886074e --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/static/js/jquery.selection.js @@ -0,0 +1,354 @@ +/*! + * jQuery.selection - jQuery Plugin + * + * Copyright (c) 2010-2014 IWASAKI Koji (@madapaja). + * http://blog.madapaja.net/ + * Under The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +(function($, win, doc) { + /** + * get caret status of the selection of the element + * + * @param {Element} element target DOM element + * @return {Object} return + * @return {String} return.text selected text + * @return {Number} return.start start position of the selection + * @return {Number} return.end end position of the selection + */ + var _getCaretInfo = function(element){ + var res = { + text: '', + start: 0, + end: 0 + }; + + if (!element.value) { + /* no value or empty string */ + return res; + } + + try { + if (win.getSelection) { + /* except IE */ + res.start = element.selectionStart; + res.end = element.selectionEnd; + res.text = element.value.slice(res.start, res.end); + } else if (doc.selection) { + /* for IE */ + element.focus(); + + var range = doc.selection.createRange(), + range2 = doc.body.createTextRange(); + + res.text = range.text; + + try { + range2.moveToElementText(element); + range2.setEndPoint('StartToStart', range); + } catch (e) { + range2 = element.createTextRange(); + range2.setEndPoint('StartToStart', range); + } + + res.start = element.value.length - range2.text.length; + res.end = res.start + range.text.length; + } + } catch (e) { + /* give up */ + } + + return res; + }; + + /** + * caret operation for the element + * @type {Object} + */ + var _CaretOperation = { + /** + * get caret position + * + * @param {Element} element target element + * @return {Object} return + * @return {Number} return.start start position for the selection + * @return {Number} return.end end position for the selection + */ + getPos: function(element) { + var tmp = _getCaretInfo(element); + return {start: tmp.start, end: tmp.end}; + }, + + /** + * set caret position + * + * @param {Element} element target element + * @param {Object} toRange caret position + * @param {Number} toRange.start start position for the selection + * @param {Number} toRange.end end position for the selection + * @param {String} caret caret mode: any of the following: "keep" | "start" | "end" + */ + setPos: function(element, toRange, caret) { + caret = this._caretMode(caret); + + if (caret === 'start') { + toRange.end = toRange.start; + } else if (caret === 'end') { + toRange.start = toRange.end; + } + + element.focus(); + try { + if (element.createTextRange) { + var range = element.createTextRange(); + + if (win.navigator.userAgent.toLowerCase().indexOf("msie") >= 0) { + toRange.start = element.value.substr(0, toRange.start).replace(/\r/g, '').length; + toRange.end = element.value.substr(0, toRange.end).replace(/\r/g, '').length; + } + + range.collapse(true); + range.moveStart('character', toRange.start); + range.moveEnd('character', toRange.end - toRange.start); + + range.select(); + } else if (element.setSelectionRange) { + element.setSelectionRange(toRange.start, toRange.end); + } + } catch (e) { + /* give up */ + } + }, + + /** + * get selected text + * + * @param {Element} element target element + * @return {String} return selected text + */ + getText: function(element) { + return _getCaretInfo(element).text; + }, + + /** + * get caret mode + * + * @param {String} caret caret mode + * @return {String} return any of the following: "keep" | "start" | "end" + */ + _caretMode: function(caret) { + caret = caret || "keep"; + if (caret === false) { + caret = 'end'; + } + + switch (caret) { + case 'keep': + case 'start': + case 'end': + break; + + default: + caret = 'keep'; + } + + return caret; + }, + + /** + * replace selected text + * + * @param {Element} element target element + * @param {String} text replacement text + * @param {String} caret caret mode: any of the following: "keep" | "start" | "end" + */ + replace: function(element, text, caret) { + var tmp = _getCaretInfo(element), + orig = element.value, + pos = $(element).scrollTop(), + range = {start: tmp.start, end: tmp.start + text.length}; + + element.value = orig.substr(0, tmp.start) + text + orig.substr(tmp.end); + + $(element).scrollTop(pos); + this.setPos(element, range, caret); + }, + + /** + * insert before the selected text + * + * @param {Element} element target element + * @param {String} text insertion text + * @param {String} caret caret mode: any of the following: "keep" | "start" | "end" + */ + insertBefore: function(element, text, caret) { + var tmp = _getCaretInfo(element), + orig = element.value, + pos = $(element).scrollTop(), + range = {start: tmp.start + text.length, end: tmp.end + text.length}; + + element.value = orig.substr(0, tmp.start) + text + orig.substr(tmp.start); + + $(element).scrollTop(pos); + this.setPos(element, range, caret); + }, + + /** + * insert after the selected text + * + * @param {Element} element target element + * @param {String} text insertion text + * @param {String} caret caret mode: any of the following: "keep" | "start" | "end" + */ + insertAfter: function(element, text, caret) { + var tmp = _getCaretInfo(element), + orig = element.value, + pos = $(element).scrollTop(), + range = {start: tmp.start, end: tmp.end}; + + element.value = orig.substr(0, tmp.end) + text + orig.substr(tmp.end); + + $(element).scrollTop(pos); + this.setPos(element, range, caret); + } + }; + + /* add jQuery.selection */ + $.extend({ + /** + * get selected text on the window + * + * @param {String} mode selection mode: any of the following: "text" | "html" + * @return {String} return + */ + selection: function(mode) { + var getText = ((mode || 'text').toLowerCase() === 'text'); + + try { + if (win.getSelection) { + if (getText) { + // get text + return win.getSelection().toString(); + } else { + // get html + var sel = win.getSelection(), range; + + if (sel.getRangeAt) { + range = sel.getRangeAt(0); + } else { + range = doc.createRange(); + range.setStart(sel.anchorNode, sel.anchorOffset); + range.setEnd(sel.focusNode, sel.focusOffset); + } + + return $('
    ').append(range.cloneContents()).html(); + } + } else if (doc.selection) { + if (getText) { + // get text + return doc.selection.createRange().text; + } else { + // get html + return doc.selection.createRange().htmlText; + } + } + } catch (e) { + /* give up */ + } + + return ''; + } + }); + + /* add selection */ + $.fn.extend({ + selection: function(mode, opts) { + opts = opts || {}; + + switch (mode) { + /** + * selection('getPos') + * get caret position + * + * @return {Object} return + * @return {Number} return.start start position for the selection + * @return {Number} return.end end position for the selection + */ + case 'getPos': + return _CaretOperation.getPos(this[0]); + + /** + * selection('setPos', opts) + * set caret position + * + * @param {Number} opts.start start position for the selection + * @param {Number} opts.end end position for the selection + */ + case 'setPos': + return this.each(function() { + _CaretOperation.setPos(this, opts); + }); + + /** + * selection('replace', opts) + * replace the selected text + * + * @param {String} opts.text replacement text + * @param {String} opts.caret caret mode: any of the following: "keep" | "start" | "end" + */ + case 'replace': + return this.each(function() { + _CaretOperation.replace(this, opts.text, opts.caret); + }); + + /** + * selection('insert', opts) + * insert before/after the selected text + * + * @param {String} opts.text insertion text + * @param {String} opts.caret caret mode: any of the following: "keep" | "start" | "end" + * @param {String} opts.mode insertion mode: any of the following: "before" | "after" + */ + case 'insert': + return this.each(function() { + if (opts.mode === 'before') { + _CaretOperation.insertBefore(this, opts.text, opts.caret); + } else { + _CaretOperation.insertAfter(this, opts.text, opts.caret); + } + }); + + /** + * selection('get') + * get selected text + * + * @return {String} return + */ + case 'get': + /* falls through */ + default: + return _CaretOperation.getText(this[0]); + } + + return this; + } + }); +})(jQuery, window, window.document); diff --git a/samples/development-frameworks/django/bootcamp/static/js/jquery.typeahead.bundle.js b/samples/development-frameworks/django/bootcamp/static/js/jquery.typeahead.bundle.js new file mode 100644 index 00000000..19631501 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/static/js/jquery.typeahead.bundle.js @@ -0,0 +1,1716 @@ +/*! + * typeahead.js 0.10.2 + * https://github.com/twitter/typeahead.js + * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT + */ + +(function($) { + var _ = { + isMsie: function() { + return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + bind: $.proxy, + each: function(collection, cb) { + $.each(collection, reverseArgs); + function reverseArgs(index, value) { + return cb(value, index); + } + }, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + getUniqueId: function() { + var counter = 0; + return function() { + return counter++; + }; + }(), + templatify: function templatify(obj) { + return $.isFunction(obj) ? obj : template; + function template() { + return String(obj); + } + }, + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + noop: function() {} + }; + var VERSION = "0.10.2"; + var tokenizers = function(root) { + return { + nonword: nonword, + whitespace: whitespace, + obj: { + nonword: getObjTokenizer(nonword), + whitespace: getObjTokenizer(whitespace) + } + }; + function whitespace(s) { + return s.split(/\s+/); + } + function nonword(s) { + return s.split(/\W+/); + } + function getObjTokenizer(tokenizer) { + return function setKey(key) { + return function tokenize(o) { + return tokenizer(o[key]); + }; + }; + } + }(); + var LruCache = function() { + function LruCache(maxSize) { + this.maxSize = maxSize || 100; + this.size = 0; + this.hash = {}; + this.list = new List(); + } + _.mixin(LruCache.prototype, { + set: function set(key, val) { + var tailItem = this.list.tail, node; + if (this.size >= this.maxSize) { + this.list.remove(tailItem); + delete this.hash[tailItem.key]; + } + if (node = this.hash[key]) { + node.val = val; + this.list.moveToFront(node); + } else { + node = new Node(key, val); + this.list.add(node); + this.hash[key] = node; + this.size++; + } + }, + get: function get(key) { + var node = this.hash[key]; + if (node) { + this.list.moveToFront(node); + return node.val; + } + } + }); + function List() { + this.head = this.tail = null; + } + _.mixin(List.prototype, { + add: function add(node) { + if (this.head) { + node.next = this.head; + this.head.prev = node; + } + this.head = node; + this.tail = this.tail || node; + }, + remove: function remove(node) { + node.prev ? node.prev.next = node.next : this.head = node.next; + node.next ? node.next.prev = node.prev : this.tail = node.prev; + }, + moveToFront: function(node) { + this.remove(node); + this.add(node); + } + }); + function Node(key, val) { + this.key = key; + this.val = val; + this.prev = this.next = null; + } + return LruCache; + }(); + var PersistentStorage = function() { + var ls, methods; + try { + ls = window.localStorage; + ls.setItem("~~~", "!"); + ls.removeItem("~~~"); + } catch (err) { + ls = null; + } + function PersistentStorage(namespace) { + this.prefix = [ "__", namespace, "__" ].join(""); + this.ttlKey = "__ttl__"; + this.keyMatcher = new RegExp("^" + this.prefix); + } + if (ls && window.JSON) { + methods = { + _prefix: function(key) { + return this.prefix + key; + }, + _ttlKey: function(key) { + return this._prefix(key) + this.ttlKey; + }, + get: function(key) { + if (this.isExpired(key)) { + this.remove(key); + } + return decode(ls.getItem(this._prefix(key))); + }, + set: function(key, val, ttl) { + if (_.isNumber(ttl)) { + ls.setItem(this._ttlKey(key), encode(now() + ttl)); + } else { + ls.removeItem(this._ttlKey(key)); + } + return ls.setItem(this._prefix(key), encode(val)); + }, + remove: function(key) { + ls.removeItem(this._ttlKey(key)); + ls.removeItem(this._prefix(key)); + return this; + }, + clear: function() { + var i, key, keys = [], len = ls.length; + for (i = 0; i < len; i++) { + if ((key = ls.key(i)).match(this.keyMatcher)) { + keys.push(key.replace(this.keyMatcher, "")); + } + } + for (i = keys.length; i--; ) { + this.remove(keys[i]); + } + return this; + }, + isExpired: function(key) { + var ttl = decode(ls.getItem(this._ttlKey(key))); + return _.isNumber(ttl) && now() > ttl ? true : false; + } + }; + } else { + methods = { + get: _.noop, + set: _.noop, + remove: _.noop, + clear: _.noop, + isExpired: _.noop + }; + } + _.mixin(PersistentStorage.prototype, methods); + return PersistentStorage; + function now() { + return new Date().getTime(); + } + function encode(val) { + return JSON.stringify(_.isUndefined(val) ? null : val); + } + function decode(val) { + return JSON.parse(val); + } + }(); + var Transport = function() { + var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, requestCache = new LruCache(10); + function Transport(o) { + o = o || {}; + this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax; + this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get; + } + Transport.setMaxPendingRequests = function setMaxPendingRequests(num) { + maxPendingRequests = num; + }; + Transport.resetCache = function clearCache() { + requestCache = new LruCache(10); + }; + _.mixin(Transport.prototype, { + _get: function(url, o, cb) { + var that = this, jqXhr; + if (jqXhr = pendingRequests[url]) { + jqXhr.done(done).fail(fail); + } else if (pendingRequestsCount < maxPendingRequests) { + pendingRequestsCount++; + pendingRequests[url] = this._send(url, o).done(done).fail(fail).always(always); + } else { + this.onDeckRequestArgs = [].slice.call(arguments, 0); + } + function done(resp) { + cb && cb(null, resp); + requestCache.set(url, resp); + } + function fail() { + cb && cb(true); + } + function always() { + pendingRequestsCount--; + delete pendingRequests[url]; + if (that.onDeckRequestArgs) { + that._get.apply(that, that.onDeckRequestArgs); + that.onDeckRequestArgs = null; + } + } + }, + get: function(url, o, cb) { + var resp; + if (_.isFunction(o)) { + cb = o; + o = {}; + } + if (resp = requestCache.get(url)) { + _.defer(function() { + cb && cb(null, resp); + }); + } else { + this._get(url, o, cb); + } + return !!resp; + } + }); + return Transport; + function callbackToDeferred(fn) { + return function customSendWrapper(url, o) { + var deferred = $.Deferred(); + fn(url, o, onSuccess, onError); + return deferred; + function onSuccess(resp) { + _.defer(function() { + deferred.resolve(resp); + }); + } + function onError(err) { + _.defer(function() { + deferred.reject(err); + }); + } + }; + } + }(); + var SearchIndex = function() { + function SearchIndex(o) { + o = o || {}; + if (!o.datumTokenizer || !o.queryTokenizer) { + $.error("datumTokenizer and queryTokenizer are both required"); + } + this.datumTokenizer = o.datumTokenizer; + this.queryTokenizer = o.queryTokenizer; + this.reset(); + } + _.mixin(SearchIndex.prototype, { + bootstrap: function bootstrap(o) { + this.datums = o.datums; + this.trie = o.trie; + }, + add: function(data) { + var that = this; + data = _.isArray(data) ? data : [ data ]; + _.each(data, function(datum) { + var id, tokens; + id = that.datums.push(datum) - 1; + tokens = normalizeTokens(that.datumTokenizer(datum)); + _.each(tokens, function(token) { + var node, chars, ch; + node = that.trie; + chars = token.split(""); + while (ch = chars.shift()) { + node = node.children[ch] || (node.children[ch] = newNode()); + node.ids.push(id); + } + }); + }); + }, + get: function get(query) { + var that = this, tokens, matches; + tokens = normalizeTokens(this.queryTokenizer(query)); + _.each(tokens, function(token) { + var node, chars, ch, ids; + if (matches && matches.length === 0) { + return false; + } + node = that.trie; + chars = token.split(""); + while (node && (ch = chars.shift())) { + node = node.children[ch]; + } + if (node && chars.length === 0) { + ids = node.ids.slice(0); + matches = matches ? getIntersection(matches, ids) : ids; + } else { + matches = []; + return false; + } + }); + return matches ? _.map(unique(matches), function(id) { + return that.datums[id]; + }) : []; + }, + reset: function reset() { + this.datums = []; + this.trie = newNode(); + }, + serialize: function serialize() { + return { + datums: this.datums, + trie: this.trie + }; + } + }); + return SearchIndex; + function normalizeTokens(tokens) { + tokens = _.filter(tokens, function(token) { + return !!token; + }); + tokens = _.map(tokens, function(token) { + return token.toLowerCase(); + }); + return tokens; + } + function newNode() { + return { + ids: [], + children: {} + }; + } + function unique(array) { + var seen = {}, uniques = []; + for (var i = 0; i < array.length; i++) { + if (!seen[array[i]]) { + seen[array[i]] = true; + uniques.push(array[i]); + } + } + return uniques; + } + function getIntersection(arrayA, arrayB) { + var ai = 0, bi = 0, intersection = []; + arrayA = arrayA.sort(compare); + arrayB = arrayB.sort(compare); + while (ai < arrayA.length && bi < arrayB.length) { + if (arrayA[ai] < arrayB[bi]) { + ai++; + } else if (arrayA[ai] > arrayB[bi]) { + bi++; + } else { + intersection.push(arrayA[ai]); + ai++; + bi++; + } + } + return intersection; + function compare(a, b) { + return a - b; + } + } + }(); + var oParser = function() { + return { + local: getLocal, + prefetch: getPrefetch, + remote: getRemote + }; + function getLocal(o) { + return o.local || null; + } + function getPrefetch(o) { + var prefetch, defaults; + defaults = { + url: null, + thumbprint: "", + ttl: 24 * 60 * 60 * 1e3, + filter: null, + ajax: {} + }; + if (prefetch = o.prefetch || null) { + prefetch = _.isString(prefetch) ? { + url: prefetch + } : prefetch; + prefetch = _.mixin(defaults, prefetch); + prefetch.thumbprint = VERSION + prefetch.thumbprint; + prefetch.ajax.type = prefetch.ajax.type || "GET"; + prefetch.ajax.dataType = prefetch.ajax.dataType || "json"; + !prefetch.url && $.error("prefetch requires url to be set"); + } + return prefetch; + } + function getRemote(o) { + var remote, defaults; + defaults = { + url: null, + wildcard: "%QUERY", + replace: null, + rateLimitBy: "debounce", + rateLimitWait: 300, + send: null, + filter: null, + ajax: {} + }; + if (remote = o.remote || null) { + remote = _.isString(remote) ? { + url: remote + } : remote; + remote = _.mixin(defaults, remote); + remote.rateLimiter = /^throttle$/i.test(remote.rateLimitBy) ? byThrottle(remote.rateLimitWait) : byDebounce(remote.rateLimitWait); + remote.ajax.type = remote.ajax.type || "GET"; + remote.ajax.dataType = remote.ajax.dataType || "json"; + delete remote.rateLimitBy; + delete remote.rateLimitWait; + !remote.url && $.error("remote requires url to be set"); + } + return remote; + function byDebounce(wait) { + return function(fn) { + return _.debounce(fn, wait); + }; + } + function byThrottle(wait) { + return function(fn) { + return _.throttle(fn, wait); + }; + } + } + }(); + (function(root) { + var old, keys; + old = root.Bloodhound; + keys = { + data: "data", + protocol: "protocol", + thumbprint: "thumbprint" + }; + root.Bloodhound = Bloodhound; + function Bloodhound(o) { + if (!o || !o.local && !o.prefetch && !o.remote) { + $.error("one of local, prefetch, or remote is required"); + } + this.limit = o.limit || 5; + this.sorter = getSorter(o.sorter); + this.dupDetector = o.dupDetector || ignoreDuplicates; + this.local = oParser.local(o); + this.prefetch = oParser.prefetch(o); + this.remote = oParser.remote(o); + this.cacheKey = this.prefetch ? this.prefetch.cacheKey || this.prefetch.url : null; + this.index = new SearchIndex({ + datumTokenizer: o.datumTokenizer, + queryTokenizer: o.queryTokenizer + }); + this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null; + } + Bloodhound.noConflict = function noConflict() { + root.Bloodhound = old; + return Bloodhound; + }; + Bloodhound.tokenizers = tokenizers; + _.mixin(Bloodhound.prototype, { + _loadPrefetch: function loadPrefetch(o) { + var that = this, serialized, deferred; + if (serialized = this._readFromStorage(o.thumbprint)) { + this.index.bootstrap(serialized); + deferred = $.Deferred().resolve(); + } else { + deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse); + } + return deferred; + function handlePrefetchResponse(resp) { + that.clear(); + that.add(o.filter ? o.filter(resp) : resp); + that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl); + } + }, + _getFromRemote: function getFromRemote(query, cb) { + var that = this, url, uriEncodedQuery; + query = query || ""; + uriEncodedQuery = encodeURIComponent(query); + url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery); + return this.transport.get(url, this.remote.ajax, handleRemoteResponse); + function handleRemoteResponse(err, resp) { + err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp); + } + }, + _saveToStorage: function saveToStorage(data, thumbprint, ttl) { + if (this.storage) { + this.storage.set(keys.data, data, ttl); + this.storage.set(keys.protocol, location.protocol, ttl); + this.storage.set(keys.thumbprint, thumbprint, ttl); + } + }, + _readFromStorage: function readFromStorage(thumbprint) { + var stored = {}, isExpired; + if (this.storage) { + stored.data = this.storage.get(keys.data); + stored.protocol = this.storage.get(keys.protocol); + stored.thumbprint = this.storage.get(keys.thumbprint); + } + isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol; + return stored.data && !isExpired ? stored.data : null; + }, + _initialize: function initialize() { + var that = this, local = this.local, deferred; + deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve(); + local && deferred.done(addLocalToIndex); + this.transport = this.remote ? new Transport(this.remote) : null; + return this.initPromise = deferred.promise(); + function addLocalToIndex() { + that.add(_.isFunction(local) ? local() : local); + } + }, + initialize: function initialize(force) { + return !this.initPromise || force ? this._initialize() : this.initPromise; + }, + add: function add(data) { + this.index.add(data); + }, + get: function get(query, cb) { + var that = this, matches = [], cacheHit = false; + matches = this.index.get(query); + matches = this.sorter(matches).slice(0, this.limit); + if (matches.length < this.limit && this.transport) { + cacheHit = this._getFromRemote(query, returnRemoteMatches); + } + if (!cacheHit) { + (matches.length > 0 || !this.transport) && cb && cb(matches); + } + function returnRemoteMatches(remoteMatches) { + var matchesWithBackfill = matches.slice(0); + _.each(remoteMatches, function(remoteMatch) { + var isDuplicate; + isDuplicate = _.some(matchesWithBackfill, function(match) { + return that.dupDetector(remoteMatch, match); + }); + !isDuplicate && matchesWithBackfill.push(remoteMatch); + return matchesWithBackfill.length < that.limit; + }); + cb && cb(that.sorter(matchesWithBackfill)); + } + }, + clear: function clear() { + this.index.reset(); + }, + clearPrefetchCache: function clearPrefetchCache() { + this.storage && this.storage.clear(); + }, + clearRemoteCache: function clearRemoteCache() { + this.transport && Transport.resetCache(); + }, + ttAdapter: function ttAdapter() { + return _.bind(this.get, this); + } + }); + return Bloodhound; + function getSorter(sortFn) { + return _.isFunction(sortFn) ? sort : noSort; + function sort(array) { + return array.sort(sortFn); + } + function noSort(array) { + return array; + } + } + function ignoreDuplicates() { + return false; + } + })(this); + var html = { + wrapper: '', + dropdown: '', + dataset: '
    ', + suggestions: '', + suggestion: '
    ' + }; + var css = { + wrapper: { + position: "relative", + display: "inline-block" + }, + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none" + }, + input: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" + }, + inputWithNoHint: { + position: "relative", + verticalAlign: "top" + }, + dropdown: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + }, + suggestions: { + display: "block" + }, + suggestion: { + whiteSpace: "nowrap", + cursor: "pointer" + }, + suggestionChild: { + whiteSpace: "normal" + }, + ltr: { + left: "0", + right: "auto" + }, + rtl: { + left: "auto", + right: " 0" + } + }; + if (_.isMsie()) { + _.mixin(css.input, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + if (_.isMsie() && _.isMsie() <= 7) { + _.mixin(css.input, { + marginTop: "-1px" + }); + } + var EventBus = function() { + var namespace = "typeahead:"; + function EventBus(o) { + if (!o || !o.el) { + $.error("EventBus initialized without el"); + } + this.$el = $(o.el); + } + _.mixin(EventBus.prototype, { + trigger: function(type) { + var args = [].slice.call(arguments, 1); + this.$el.trigger(namespace + type, args); + } + }); + return EventBus; + }(); + var EventEmitter = function() { + var splitter = /\s+/, nextTick = getNextTick(); + return { + onSync: onSync, + onAsync: onAsync, + off: off, + trigger: trigger + }; + function on(method, types, cb, context) { + var type; + if (!cb) { + return this; + } + types = types.split(splitter); + cb = context ? bindContext(cb, context) : cb; + this._callbacks = this._callbacks || {}; + while (type = types.shift()) { + this._callbacks[type] = this._callbacks[type] || { + sync: [], + async: [] + }; + this._callbacks[type][method].push(cb); + } + return this; + } + function onAsync(types, cb, context) { + return on.call(this, "async", types, cb, context); + } + function onSync(types, cb, context) { + return on.call(this, "sync", types, cb, context); + } + function off(types) { + var type; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + while (type = types.shift()) { + delete this._callbacks[type]; + } + return this; + } + function trigger(types) { + var type, callbacks, args, syncFlush, asyncFlush; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + args = [].slice.call(arguments, 1); + while ((type = types.shift()) && (callbacks = this._callbacks[type])) { + syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); + asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); + syncFlush() && nextTick(asyncFlush); + } + return this; + } + function getFlush(callbacks, context, args) { + return flush; + function flush() { + var cancelled; + for (var i = 0; !cancelled && i < callbacks.length; i += 1) { + cancelled = callbacks[i].apply(context, args) === false; + } + return !cancelled; + } + } + function getNextTick() { + var nextTickFn; + if (window.setImmediate) { + nextTickFn = function nextTickSetImmediate(fn) { + setImmediate(function() { + fn(); + }); + }; + } else { + nextTickFn = function nextTickSetTimeout(fn) { + setTimeout(function() { + fn(); + }, 0); + }; + } + return nextTickFn; + } + function bindContext(fn, context) { + return fn.bind ? fn.bind(context) : function() { + fn.apply(context, [].slice.call(arguments, 0)); + }; + } + }(); + var highlight = function(doc) { + var defaults = { + node: null, + pattern: null, + tagName: "strong", + className: null, + wordsOnly: false, + caseSensitive: false + }; + return function hightlight(o) { + var regex; + o = _.mixin({}, defaults, o); + if (!o.node || !o.pattern) { + return; + } + o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; + regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); + traverse(o.node, hightlightTextNode); + function hightlightTextNode(textNode) { + var match, patternNode; + if (match = regex.exec(textNode.data)) { + wrapperNode = doc.createElement(o.tagName); + o.className && (wrapperNode.className = o.className); + patternNode = textNode.splitText(match.index); + patternNode.splitText(match[0].length); + wrapperNode.appendChild(patternNode.cloneNode(true)); + textNode.parentNode.replaceChild(wrapperNode, patternNode); + } + return !!match; + } + function traverse(el, hightlightTextNode) { + var childNode, TEXT_NODE_TYPE = 3; + for (var i = 0; i < el.childNodes.length; i++) { + childNode = el.childNodes[i]; + if (childNode.nodeType === TEXT_NODE_TYPE) { + i += hightlightTextNode(childNode) ? 1 : 0; + } else { + traverse(childNode, hightlightTextNode); + } + } + } + }; + function getRegex(patterns, caseSensitive, wordsOnly) { + var escapedPatterns = [], regexStr; + for (var i = 0; i < patterns.length; i++) { + escapedPatterns.push(_.escapeRegExChars(patterns[i])); + } + regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; + return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); + } + }(window.document); + var Input = function() { + var specialKeyCodeMap; + specialKeyCodeMap = { + 9: "tab", + 27: "esc", + 37: "left", + 39: "right", + 13: "enter", + 38: "up", + 40: "down" + }; + function Input(o) { + var that = this, onBlur, onFocus, onKeydown, onInput; + o = o || {}; + if (!o.input) { + $.error("input is missing"); + } + onBlur = _.bind(this._onBlur, this); + onFocus = _.bind(this._onFocus, this); + onKeydown = _.bind(this._onKeydown, this); + onInput = _.bind(this._onInput, this); + this.$hint = $(o.hint); + this.$input = $(o.input).on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); + if (this.$hint.length === 0) { + this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; + } + if (!_.isMsie()) { + this.$input.on("input.tt", onInput); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { + if (specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + _.defer(_.bind(that._onInput, that, $e)); + }); + } + this.query = this.$input.val(); + this.$overflowHelper = buildOverflowHelper(this.$input); + } + Input.normalizeQuery = function(str) { + return (str || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + }; + _.mixin(Input.prototype, EventEmitter, { + _onBlur: function onBlur() { + this.resetInputValue(); + this.trigger("blurred"); + }, + _onFocus: function onFocus() { + this.trigger("focused"); + }, + _onKeydown: function onKeydown($e) { + var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; + this._managePreventDefault(keyName, $e); + if (keyName && this._shouldTrigger(keyName, $e)) { + this.trigger(keyName + "Keyed", $e); + } + }, + _onInput: function onInput() { + this._checkInputValue(); + }, + _managePreventDefault: function managePreventDefault(keyName, $e) { + var preventDefault, hintValue, inputValue; + switch (keyName) { + case "tab": + hintValue = this.getHint(); + inputValue = this.getInputValue(); + preventDefault = hintValue && hintValue !== inputValue && !withModifier($e); + break; + + case "up": + case "down": + preventDefault = !withModifier($e); + break; + + default: + preventDefault = false; + } + preventDefault && $e.preventDefault(); + }, + _shouldTrigger: function shouldTrigger(keyName, $e) { + var trigger; + switch (keyName) { + case "tab": + trigger = !withModifier($e); + break; + + default: + trigger = true; + } + return trigger; + }, + _checkInputValue: function checkInputValue() { + var inputValue, areEquivalent, hasDifferentWhitespace; + inputValue = this.getInputValue(); + areEquivalent = areQueriesEquivalent(inputValue, this.query); + hasDifferentWhitespace = areEquivalent ? this.query.length !== inputValue.length : false; + if (!areEquivalent) { + this.trigger("queryChanged", this.query = inputValue); + } else if (hasDifferentWhitespace) { + this.trigger("whitespaceChanged", this.query); + } + }, + focus: function focus() { + this.$input.focus(); + }, + blur: function blur() { + this.$input.blur(); + }, + getQuery: function getQuery() { + return this.query; + }, + setQuery: function setQuery(query) { + this.query = query; + }, + getInputValue: function getInputValue() { + return this.$input.val(); + }, + setInputValue: function setInputValue(value, silent) { + this.$input.val(value); + silent ? this.clearHint() : this._checkInputValue(); + }, + resetInputValue: function resetInputValue() { + this.setInputValue(this.query, true); + }, + getHint: function getHint() { + return this.$hint.val(); + }, + setHint: function setHint(value) { + this.$hint.val(value); + }, + clearHint: function clearHint() { + this.setHint(""); + }, + clearHintIfInvalid: function clearHintIfInvalid() { + var val, hint, valIsPrefixOfHint, isValid; + val = this.getInputValue(); + hint = this.getHint(); + valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; + isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); + !isValid && this.clearHint(); + }, + getLanguageDirection: function getLanguageDirection() { + return (this.$input.css("direction") || "ltr").toLowerCase(); + }, + hasOverflow: function hasOverflow() { + var constraint = this.$input.width() - 2; + this.$overflowHelper.text(this.getInputValue()); + return this.$overflowHelper.width() >= constraint; + }, + isCursorAtEnd: function() { + var valueLength, selectionStart, range; + valueLength = this.$input.val().length; + selectionStart = this.$input[0].selectionStart; + if (_.isNumber(selectionStart)) { + return selectionStart === valueLength; + } else if (document.selection) { + range = document.selection.createRange(); + range.moveStart("character", -valueLength); + return valueLength === range.text.length; + } + return true; + }, + destroy: function destroy() { + this.$hint.off(".tt"); + this.$input.off(".tt"); + this.$hint = this.$input = this.$overflowHelper = null; + } + }); + return Input; + function buildOverflowHelper($input) { + return $('').css({ + position: "absolute", + visibility: "hidden", + whiteSpace: "pre", + fontFamily: $input.css("font-family"), + fontSize: $input.css("font-size"), + fontStyle: $input.css("font-style"), + fontVariant: $input.css("font-variant"), + fontWeight: $input.css("font-weight"), + wordSpacing: $input.css("word-spacing"), + letterSpacing: $input.css("letter-spacing"), + textIndent: $input.css("text-indent"), + textRendering: $input.css("text-rendering"), + textTransform: $input.css("text-transform") + }).insertAfter($input); + } + function areQueriesEquivalent(a, b) { + return Input.normalizeQuery(a) === Input.normalizeQuery(b); + } + function withModifier($e) { + return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; + } + }(); + var Dataset = function() { + var datasetKey = "ttDataset", valueKey = "ttValue", datumKey = "ttDatum"; + function Dataset(o) { + o = o || {}; + o.templates = o.templates || {}; + if (!o.source) { + $.error("missing source"); + } + if (o.name && !isValidName(o.name)) { + $.error("invalid dataset name: " + o.name); + } + this.query = null; + this.highlight = !!o.highlight; + this.name = o.name || _.getUniqueId(); + this.source = o.source; + this.displayFn = getDisplayFn(o.display || o.displayKey); + this.templates = getTemplates(o.templates, this.displayFn); + this.$el = $(html.dataset.replace("%CLASS%", this.name)); + } + Dataset.extractDatasetName = function extractDatasetName(el) { + return $(el).data(datasetKey); + }; + Dataset.extractValue = function extractDatum(el) { + return $(el).data(valueKey); + }; + Dataset.extractDatum = function extractDatum(el) { + return $(el).data(datumKey); + }; + _.mixin(Dataset.prototype, EventEmitter, { + _render: function render(query, suggestions) { + if (!this.$el) { + return; + } + var that = this, hasSuggestions; + this.$el.empty(); + hasSuggestions = suggestions && suggestions.length; + if (!hasSuggestions && this.templates.empty) { + this.$el.html(getEmptyHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null); + } else if (hasSuggestions) { + this.$el.html(getSuggestionsHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null); + } + this.trigger("rendered"); + function getEmptyHtml() { + return that.templates.empty({ + query: query, + isEmpty: true + }); + } + function getSuggestionsHtml() { + var $suggestions, nodes; + $suggestions = $(html.suggestions).css(css.suggestions); + nodes = _.map(suggestions, getSuggestionNode); + $suggestions.append.apply($suggestions, nodes); + that.highlight && highlight({ + node: $suggestions[0], + pattern: query + }); + return $suggestions; + function getSuggestionNode(suggestion) { + var $el; + $el = $(html.suggestion).append(that.templates.suggestion(suggestion)).data(datasetKey, that.name).data(valueKey, that.displayFn(suggestion)).data(datumKey, suggestion); + $el.children().each(function() { + $(this).css(css.suggestionChild); + }); + return $el; + } + } + function getHeaderHtml() { + return that.templates.header({ + query: query, + isEmpty: !hasSuggestions + }); + } + function getFooterHtml() { + return that.templates.footer({ + query: query, + isEmpty: !hasSuggestions + }); + } + }, + getRoot: function getRoot() { + return this.$el; + }, + update: function update(query) { + var that = this; + this.query = query; + this.canceled = false; + this.source(query, render); + function render(suggestions) { + if (!that.canceled && query === that.query) { + that._render(query, suggestions); + } + } + }, + cancel: function cancel() { + this.canceled = true; + }, + clear: function clear() { + this.cancel(); + this.$el.empty(); + this.trigger("rendered"); + }, + isEmpty: function isEmpty() { + return this.$el.is(":empty"); + }, + destroy: function destroy() { + this.$el = null; + } + }); + return Dataset; + function getDisplayFn(display) { + display = display || "value"; + return _.isFunction(display) ? display : displayFn; + function displayFn(obj) { + return obj[display]; + } + } + function getTemplates(templates, displayFn) { + return { + empty: templates.empty && _.templatify(templates.empty), + header: templates.header && _.templatify(templates.header), + footer: templates.footer && _.templatify(templates.footer), + suggestion: templates.suggestion || suggestionTemplate + }; + function suggestionTemplate(context) { + return "

    " + displayFn(context) + "

    "; + } + } + function isValidName(str) { + return /^[_a-zA-Z0-9-]+$/.test(str); + } + }(); + var Dropdown = function() { + function Dropdown(o) { + var that = this, onSuggestionClick, onSuggestionMouseEnter, onSuggestionMouseLeave; + o = o || {}; + if (!o.menu) { + $.error("menu is required"); + } + this.isOpen = false; + this.isEmpty = true; + this.datasets = _.map(o.datasets, initializeDataset); + onSuggestionClick = _.bind(this._onSuggestionClick, this); + onSuggestionMouseEnter = _.bind(this._onSuggestionMouseEnter, this); + onSuggestionMouseLeave = _.bind(this._onSuggestionMouseLeave, this); + this.$menu = $(o.menu).on("click.tt", ".tt-suggestion", onSuggestionClick).on("mouseenter.tt", ".tt-suggestion", onSuggestionMouseEnter).on("mouseleave.tt", ".tt-suggestion", onSuggestionMouseLeave); + _.each(this.datasets, function(dataset) { + that.$menu.append(dataset.getRoot()); + dataset.onSync("rendered", that._onRendered, that); + }); + } + _.mixin(Dropdown.prototype, EventEmitter, { + _onSuggestionClick: function onSuggestionClick($e) { + this.trigger("suggestionClicked", $($e.currentTarget)); + }, + _onSuggestionMouseEnter: function onSuggestionMouseEnter($e) { + this._removeCursor(); + this._setCursor($($e.currentTarget), true); + }, + _onSuggestionMouseLeave: function onSuggestionMouseLeave() { + this._removeCursor(); + }, + _onRendered: function onRendered() { + this.isEmpty = _.every(this.datasets, isDatasetEmpty); + this.isEmpty ? this._hide() : this.isOpen && this._show(); + this.trigger("datasetRendered"); + function isDatasetEmpty(dataset) { + return dataset.isEmpty(); + } + }, + _hide: function() { + this.$menu.hide(); + }, + _show: function() { + this.$menu.css("display", "block"); + }, + _getSuggestions: function getSuggestions() { + return this.$menu.find(".tt-suggestion"); + }, + _getCursor: function getCursor() { + return this.$menu.find(".tt-cursor").first(); + }, + _setCursor: function setCursor($el, silent) { + $el.first().addClass("tt-cursor"); + !silent && this.trigger("cursorMoved"); + }, + _removeCursor: function removeCursor() { + this._getCursor().removeClass("tt-cursor"); + }, + _moveCursor: function moveCursor(increment) { + var $suggestions, $oldCursor, newCursorIndex, $newCursor; + if (!this.isOpen) { + return; + } + $oldCursor = this._getCursor(); + $suggestions = this._getSuggestions(); + this._removeCursor(); + newCursorIndex = $suggestions.index($oldCursor) + increment; + newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1; + if (newCursorIndex === -1) { + this.trigger("cursorRemoved"); + return; + } else if (newCursorIndex < -1) { + newCursorIndex = $suggestions.length - 1; + } + this._setCursor($newCursor = $suggestions.eq(newCursorIndex)); + this._ensureVisible($newCursor); + }, + _ensureVisible: function ensureVisible($el) { + var elTop, elBottom, menuScrollTop, menuHeight; + elTop = $el.position().top; + elBottom = elTop + $el.outerHeight(true); + menuScrollTop = this.$menu.scrollTop(); + menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10); + if (elTop < 0) { + this.$menu.scrollTop(menuScrollTop + elTop); + } else if (menuHeight < elBottom) { + this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); + } + }, + close: function close() { + if (this.isOpen) { + this.isOpen = false; + this._removeCursor(); + this._hide(); + this.trigger("closed"); + } + }, + open: function open() { + if (!this.isOpen) { + this.isOpen = true; + !this.isEmpty && this._show(); + this.trigger("opened"); + } + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$menu.css(dir === "ltr" ? css.ltr : css.rtl); + }, + moveCursorUp: function moveCursorUp() { + this._moveCursor(-1); + }, + moveCursorDown: function moveCursorDown() { + this._moveCursor(+1); + }, + getDatumForSuggestion: function getDatumForSuggestion($el) { + var datum = null; + if ($el.length) { + datum = { + raw: Dataset.extractDatum($el), + value: Dataset.extractValue($el), + datasetName: Dataset.extractDatasetName($el) + }; + } + return datum; + }, + getDatumForCursor: function getDatumForCursor() { + return this.getDatumForSuggestion(this._getCursor().first()); + }, + getDatumForTopSuggestion: function getDatumForTopSuggestion() { + return this.getDatumForSuggestion(this._getSuggestions().first()); + }, + update: function update(query) { + _.each(this.datasets, updateDataset); + function updateDataset(dataset) { + dataset.update(query); + } + }, + empty: function empty() { + _.each(this.datasets, clearDataset); + this.isEmpty = true; + function clearDataset(dataset) { + dataset.clear(); + } + }, + isVisible: function isVisible() { + return this.isOpen && !this.isEmpty; + }, + destroy: function destroy() { + this.$menu.off(".tt"); + this.$menu = null; + _.each(this.datasets, destroyDataset); + function destroyDataset(dataset) { + dataset.destroy(); + } + } + }); + return Dropdown; + function initializeDataset(oDataset) { + return new Dataset(oDataset); + } + }(); + var Typeahead = function() { + var attrsKey = "ttAttrs"; + function Typeahead(o) { + var $menu, $input, $hint; + o = o || {}; + if (!o.input) { + $.error("missing input"); + } + this.isActivated = false; + this.autoselect = !!o.autoselect; + this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; + this.$node = buildDomStructure(o.input, o.withHint); + $menu = this.$node.find(".tt-dropdown-menu"); + $input = this.$node.find(".tt-input"); + $hint = this.$node.find(".tt-hint"); + $input.on("blur.tt", function($e) { + var active, isActive, hasActive; + active = document.activeElement; + isActive = $menu.is(active); + hasActive = $menu.has(active).length > 0; + if (_.isMsie() && (isActive || hasActive)) { + $e.preventDefault(); + $e.stopImmediatePropagation(); + _.defer(function() { + $input.focus(); + }); + } + }); + $menu.on("mousedown.tt", function($e) { + $e.preventDefault(); + }); + this.eventBus = o.eventBus || new EventBus({ + el: $input + }); + this.dropdown = new Dropdown({ + menu: $menu, + datasets: o.datasets + }).onSync("suggestionClicked", this._onSuggestionClicked, this).onSync("cursorMoved", this._onCursorMoved, this).onSync("cursorRemoved", this._onCursorRemoved, this).onSync("opened", this._onOpened, this).onSync("closed", this._onClosed, this).onAsync("datasetRendered", this._onDatasetRendered, this); + this.input = new Input({ + input: $input, + hint: $hint + }).onSync("focused", this._onFocused, this).onSync("blurred", this._onBlurred, this).onSync("enterKeyed", this._onEnterKeyed, this).onSync("tabKeyed", this._onTabKeyed, this).onSync("escKeyed", this._onEscKeyed, this).onSync("upKeyed", this._onUpKeyed, this).onSync("downKeyed", this._onDownKeyed, this).onSync("leftKeyed", this._onLeftKeyed, this).onSync("rightKeyed", this._onRightKeyed, this).onSync("queryChanged", this._onQueryChanged, this).onSync("whitespaceChanged", this._onWhitespaceChanged, this); + this._setLanguageDirection(); + } + _.mixin(Typeahead.prototype, { + _onSuggestionClicked: function onSuggestionClicked(type, $el) { + var datum; + if (datum = this.dropdown.getDatumForSuggestion($el)) { + this._select(datum); + } + }, + _onCursorMoved: function onCursorMoved() { + var datum = this.dropdown.getDatumForCursor(); + this.input.setInputValue(datum.value, true); + this.eventBus.trigger("cursorchanged", datum.raw, datum.datasetName); + }, + _onCursorRemoved: function onCursorRemoved() { + this.input.resetInputValue(); + this._updateHint(); + }, + _onDatasetRendered: function onDatasetRendered() { + this._updateHint(); + }, + _onOpened: function onOpened() { + this._updateHint(); + this.eventBus.trigger("opened"); + }, + _onClosed: function onClosed() { + this.input.clearHint(); + this.eventBus.trigger("closed"); + }, + _onFocused: function onFocused() { + this.isActivated = true; + this.dropdown.open(); + }, + _onBlurred: function onBlurred() { + this.isActivated = false; + this.dropdown.empty(); + this.dropdown.close(); + }, + _onEnterKeyed: function onEnterKeyed(type, $e) { + var cursorDatum, topSuggestionDatum; + cursorDatum = this.dropdown.getDatumForCursor(); + topSuggestionDatum = this.dropdown.getDatumForTopSuggestion(); + if (cursorDatum) { + this._select(cursorDatum); + $e.preventDefault(); + } else if (this.autoselect && topSuggestionDatum) { + this._select(topSuggestionDatum); + $e.preventDefault(); + } + }, + _onTabKeyed: function onTabKeyed(type, $e) { + var datum; + if (datum = this.dropdown.getDatumForCursor()) { + this._select(datum); + $e.preventDefault(); + } else { + this._autocomplete(true); + } + }, + _onEscKeyed: function onEscKeyed() { + this.dropdown.close(); + this.input.resetInputValue(); + }, + _onUpKeyed: function onUpKeyed() { + var query = this.input.getQuery(); + this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorUp(); + this.dropdown.open(); + }, + _onDownKeyed: function onDownKeyed() { + var query = this.input.getQuery(); + this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorDown(); + this.dropdown.open(); + }, + _onLeftKeyed: function onLeftKeyed() { + this.dir === "rtl" && this._autocomplete(); + }, + _onRightKeyed: function onRightKeyed() { + this.dir === "ltr" && this._autocomplete(); + }, + _onQueryChanged: function onQueryChanged(e, query) { + this.input.clearHintIfInvalid(); + query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.empty(); + this.dropdown.open(); + this._setLanguageDirection(); + }, + _onWhitespaceChanged: function onWhitespaceChanged() { + this._updateHint(); + this.dropdown.open(); + }, + _setLanguageDirection: function setLanguageDirection() { + var dir; + if (this.dir !== (dir = this.input.getLanguageDirection())) { + this.dir = dir; + this.$node.css("direction", dir); + this.dropdown.setLanguageDirection(dir); + } + }, + _updateHint: function updateHint() { + var datum, val, query, escapedQuery, frontMatchRegEx, match; + datum = this.dropdown.getDatumForTopSuggestion(); + if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) { + val = this.input.getInputValue(); + query = Input.normalizeQuery(val); + escapedQuery = _.escapeRegExChars(query); + frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); + match = frontMatchRegEx.exec(datum.value); + match ? this.input.setHint(val + match[1]) : this.input.clearHint(); + } else { + this.input.clearHint(); + } + }, + _autocomplete: function autocomplete(laxCursor) { + var hint, query, isCursorAtEnd, datum; + hint = this.input.getHint(); + query = this.input.getQuery(); + isCursorAtEnd = laxCursor || this.input.isCursorAtEnd(); + if (hint && query !== hint && isCursorAtEnd) { + datum = this.dropdown.getDatumForTopSuggestion(); + datum && this.input.setInputValue(datum.value); + this.eventBus.trigger("autocompleted", datum.raw, datum.datasetName); + } + }, + _select: function select(datum) { + this.input.setQuery(datum.value); + this.input.setInputValue(datum.value, true); + this._setLanguageDirection(); + this.eventBus.trigger("selected", datum.raw, datum.datasetName); + this.dropdown.close(); + _.defer(_.bind(this.dropdown.empty, this.dropdown)); + }, + open: function open() { + this.dropdown.open(); + }, + close: function close() { + this.dropdown.close(); + }, + setVal: function setVal(val) { + if (this.isActivated) { + this.input.setInputValue(val); + } else { + this.input.setQuery(val); + this.input.setInputValue(val, true); + } + this._setLanguageDirection(); + }, + getVal: function getVal() { + return this.input.getQuery(); + }, + destroy: function destroy() { + this.input.destroy(); + this.dropdown.destroy(); + destroyDomStructure(this.$node); + this.$node = null; + } + }); + return Typeahead; + function buildDomStructure(input, withHint) { + var $input, $wrapper, $dropdown, $hint; + $input = $(input); + $wrapper = $(html.wrapper).css(css.wrapper); + $dropdown = $(html.dropdown).css(css.dropdown); + $hint = $input.clone().css(css.hint).css(getBackgroundStyles($input)); + $hint.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder").prop("disabled", true).attr({ + autocomplete: "off", + spellcheck: "false" + }); + $input.data(attrsKey, { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass("tt-input").attr({ + autocomplete: "off", + spellcheck: false + }).css(withHint ? css.input : css.inputWithNoHint); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) {} + return $input.wrap($wrapper).parent().prepend(withHint ? $hint : null).append($dropdown); + } + function getBackgroundStyles($el) { + return { + backgroundAttachment: $el.css("background-attachment"), + backgroundClip: $el.css("background-clip"), + backgroundColor: $el.css("background-color"), + backgroundImage: $el.css("background-image"), + backgroundOrigin: $el.css("background-origin"), + backgroundPosition: $el.css("background-position"), + backgroundRepeat: $el.css("background-repeat"), + backgroundSize: $el.css("background-size") + }; + } + function destroyDomStructure($node) { + var $input = $node.find(".tt-input"); + _.each($input.data(attrsKey), function(val, key) { + _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.detach().removeData(attrsKey).removeClass("tt-input").insertAfter($node); + $node.remove(); + } + }(); + (function() { + var old, typeaheadKey, methods; + old = $.fn.typeahead; + typeaheadKey = "ttTypeahead"; + methods = { + initialize: function initialize(o, datasets) { + datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); + o = o || {}; + return this.each(attach); + function attach() { + var $input = $(this), eventBus, typeahead; + _.each(datasets, function(d) { + d.highlight = !!o.highlight; + }); + typeahead = new Typeahead({ + input: $input, + eventBus: eventBus = new EventBus({ + el: $input + }), + withHint: _.isUndefined(o.hint) ? true : !!o.hint, + minLength: o.minLength, + autoselect: o.autoselect, + datasets: datasets + }); + $input.data(typeaheadKey, typeahead); + } + }, + open: function open() { + return this.each(openTypeahead); + function openTypeahead() { + var $input = $(this), typeahead; + if (typeahead = $input.data(typeaheadKey)) { + typeahead.open(); + } + } + }, + close: function close() { + return this.each(closeTypeahead); + function closeTypeahead() { + var $input = $(this), typeahead; + if (typeahead = $input.data(typeaheadKey)) { + typeahead.close(); + } + } + }, + val: function val(newVal) { + return !arguments.length ? getVal(this.first()) : this.each(setVal); + function setVal() { + var $input = $(this), typeahead; + if (typeahead = $input.data(typeaheadKey)) { + typeahead.setVal(newVal); + } + } + function getVal($input) { + var typeahead, query; + if (typeahead = $input.data(typeaheadKey)) { + query = typeahead.getVal(); + } + return query; + } + }, + destroy: function destroy() { + return this.each(unattach); + function unattach() { + var $input = $(this), typeahead; + if (typeahead = $input.data(typeaheadKey)) { + typeahead.destroy(); + $input.removeData(typeaheadKey); + } + } + } + }; + $.fn.typeahead = function(method) { + if (methods[method]) { + return methods[method].apply(this, [].slice.call(arguments, 1)); + } else { + return methods.initialize.apply(this, arguments); + } + }; + $.fn.typeahead.noConflict = function noConflict() { + $.fn.typeahead = old; + return this; + }; + })(); +})(window.jQuery); \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/templates/403.html b/samples/development-frameworks/django/bootcamp/templates/403.html new file mode 100644 index 00000000..42a4ecef --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/templates/403.html @@ -0,0 +1,8 @@ +{% extends 'base.html' %} + +{% block main %} + +

    Hey you!

    +{% endblock main %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/templates/404.html b/samples/development-frameworks/django/bootcamp/templates/404.html new file mode 100644 index 00000000..59162dd2 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/templates/404.html @@ -0,0 +1,8 @@ +{% extends 'base.html' %} + +{% block main %} + +

    We could not find the page you are looking for :(

    +{% endblock main %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/templates/500.html b/samples/development-frameworks/django/bootcamp/templates/500.html new file mode 100644 index 00000000..6aabf4e6 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/templates/500.html @@ -0,0 +1,8 @@ +{% extends 'base.html' %} + +{% block main %} + +

    Ooops! Something went wrong. That's all we know.

    +{% endblock main %} \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/templates/base.html b/samples/development-frameworks/django/bootcamp/templates/base.html new file mode 100644 index 00000000..f5ccc86a --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/templates/base.html @@ -0,0 +1,85 @@ +{% load staticfiles i18n %} + + + + + + {% block title %}Bootcamp{% endblock %} + + + + + + + + {% block head %}{% endblock %} + + + {% block body %} +
    + +
    +
    +
    + {% block main %} + {% endblock main %} +
    +
    + + + {% endblock body %} + + + diff --git a/samples/development-frameworks/django/bootcamp/templates/markdown_editor.html b/samples/development-frameworks/django/bootcamp/templates/markdown_editor.html new file mode 100644 index 00000000..2a12969f --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/templates/markdown_editor.html @@ -0,0 +1,82 @@ +{% load staticfiles %} + + + +
    + + + +
    + + + + + + + + +
    + +
    + \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/templates/paginator.html b/samples/development-frameworks/django/bootcamp/templates/paginator.html new file mode 100644 index 00000000..2d8f29f1 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/templates/paginator.html @@ -0,0 +1,19 @@ +
      + {% if paginator.has_previous %} +
    • «
    • + {% else %} +
    • «
    • + {% endif %} + {% for i in paginator.paginator.page_range %} + {% if paginator.number == i %} +
    • {{ i }} (current)
    • + {% else %} +
    • {{ i }}
    • + {% endif %} + {% endfor %} + {% if paginator.has_next %} +
    • »
    • + {% else %} +
    • »
    • + {% endif %} +
    \ No newline at end of file diff --git a/samples/development-frameworks/django/bootcamp/urls.py b/samples/development-frameworks/django/bootcamp/urls.py new file mode 100644 index 00000000..30cd75e4 --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/urls.py @@ -0,0 +1,45 @@ +# coding: utf-8 + +from django.conf.urls import include, url +from django.conf import settings +from django.conf.urls.static import static +from django.contrib.auth import views as auth_views + +from bootcamp.core import views as core_views +from bootcamp.authentication import views as bootcamp_auth_views +from bootcamp.activities import views as activities_views +from bootcamp.search import views as search_views + + +urlpatterns = [ + url(r'^$', core_views.home, name='home'), + url(r'^login', auth_views.login, {'template_name': 'core/cover.html'}, + name='login'), + url(r'^logout', auth_views.logout, {'next_page': '/'}, name='logout'), + url(r'^signup/$', bootcamp_auth_views.signup, name='signup'), + url(r'^settings/$', core_views.settings, name='settings'), + url(r'^settings/picture/$', core_views.picture, name='picture'), + url(r'^settings/upload_picture/$', core_views.upload_picture, + name='upload_picture'), + url(r'^settings/save_uploaded_picture/$', core_views.save_uploaded_picture, + name='save_uploaded_picture'), + url(r'^settings/password/$', core_views.password, name='password'), + url(r'^network/$', core_views.network, name='network'), + url(r'^feeds/', include('bootcamp.feeds.urls')), + url(r'^questions/', include('bootcamp.questions.urls')), + url(r'^articles/', include('bootcamp.articles.urls')), + url(r'^messages/', include('bootcamp.messenger.urls')), + url(r'^notifications/$', activities_views.notifications, + name='notifications'), + url(r'^notifications/last/$', activities_views.last_notifications, + name='last_notifications'), + url(r'^notifications/check/$', activities_views.check_notifications, + name='check_notifications'), + url(r'^search/$', search_views.search, name='search'), + url(r'^(?P[^/]+)/$', core_views.profile, name='profile'), + url(r'^i18n/', include('django.conf.urls.i18n', namespace='i18n')), +] + +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, + document_root=settings.MEDIA_ROOT) diff --git a/samples/development-frameworks/django/bootcamp/wsgi.py b/samples/development-frameworks/django/bootcamp/wsgi.py new file mode 100644 index 00000000..0331415d --- /dev/null +++ b/samples/development-frameworks/django/bootcamp/wsgi.py @@ -0,0 +1,9 @@ +import os +from django.core.wsgi import get_wsgi_application +from whitenoise.django import DjangoWhiteNoise + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bootcamp.settings") + +application = get_wsgi_application() +application = DjangoWhiteNoise(application) + diff --git a/samples/development-frameworks/django/manage.py b/samples/development-frameworks/django/manage.py new file mode 100644 index 00000000..978f21db --- /dev/null +++ b/samples/development-frameworks/django/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bootcamp.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/samples/development-frameworks/django/requirements.txt b/samples/development-frameworks/django/requirements.txt new file mode 100644 index 00000000..61ae6b73 --- /dev/null +++ b/samples/development-frameworks/django/requirements.txt @@ -0,0 +1,11 @@ +Django==1.9.8 +dj-database-url==0.3.0 +dj-static==0.0.6 +gunicorn==19.6.0 +Unipath==1.0 +python-decouple==3 +Pillow==3.3.0 +Markdown==2.6.6 +bleach==1.4.3 +django-pyodbc-azure==1.9.6.0 +whitenoise==3.2 diff --git a/samples/development-frameworks/django/runtime.txt b/samples/development-frameworks/django/runtime.txt new file mode 100644 index 00000000..fdf79660 --- /dev/null +++ b/samples/development-frameworks/django/runtime.txt @@ -0,0 +1 @@ +python-2.7.12