Skip to content
Browse files

Merge branch 'ignite_templates' of github.com:rossbruniges/mozilla-ig…

…nite into ignite_templates
  • Loading branch information...
2 parents dcaf7e1 + 0545af2 commit e3c1efb0ab93539d4dcac8ec2b00d6499bf1d898 @rossbruniges committed
View
1 apps/challenges/forms.py
@@ -11,5 +11,4 @@ class Meta:
'title',
'brief_description',
'description',
- 'created_by'
)
View
145 apps/challenges/migrations/0007_change_summary_to_charfield.py
@@ -0,0 +1,145 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Changing field 'Submission.brief_description'
+ db.alter_column('challenges_submission', 'brief_description', self.gf('django.db.models.fields.CharField')(max_length=200))
+
+
+ def backwards(self, orm):
+
+ # Changing field 'Submission.brief_description'
+ db.alter_column('challenges_submission', 'brief_description', self.gf('django.db.models.fields.TextField')())
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'challenges.challenge': {
+ 'Meta': {'object_name': 'Challenge'},
+ 'allow_voting': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {}),
+ 'end_date': ('django.db.models.fields.DateTimeField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'moderate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '60', 'db_index': 'True'}),
+ 'start_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'summary': ('django.db.models.fields.TextField', [], {}),
+ 'title': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '60'})
+ },
+ 'challenges.phase': {
+ 'Meta': {'ordering': "('order',)", 'unique_together': "(('challenge', 'name'),)", 'object_name': 'Phase'},
+ 'challenge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'phases'", 'to': "orm['challenges.Challenge']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {})
+ },
+ 'challenges.submission': {
+ 'Meta': {'ordering': "['-id']", 'object_name': 'Submission'},
+ 'brief_description': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'created_by': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['users.Profile']", 'symmetrical': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {}),
+ 'flagged_offensive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'flagged_offensive_reason': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_live': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_winner': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'phase': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['challenges.Phase']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '60'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'projects.project': {
+ 'Meta': {'object_name': 'Project'},
+ 'allow_participation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'allow_sub_projects': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'featured': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'featured_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'followers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects_following'", 'symmetrical': 'False', 'to': "orm['users.Profile']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'long_description': ('django.db.models.fields.TextField', [], {}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'parent_project_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'}),
+ 'sub_project_label': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'team_members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['users.Profile']", 'symmetrical': 'False'}),
+ 'topics': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['topics.Topic']", 'symmetrical': 'False'})
+ },
+ 'taggit.tag': {
+ 'Meta': {'object_name': 'Tag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'})
+ },
+ 'taggit.taggeditem': {
+ 'Meta': {'object_name': 'TaggedItem'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
+ },
+ 'topics.topic': {
+ 'Meta': {'object_name': 'Topic'},
+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'draft': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
+ 'long_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'})
+ },
+ 'users.profile': {
+ 'Meta': {'object_name': 'Profile'},
+ 'avatar': ('django.db.models.fields.files.ImageField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
+ 'bio': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'featured': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'featured_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),
+ 'website': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['challenges']
View
50 apps/challenges/models.py
@@ -1,6 +1,7 @@
from datetime import datetime
from django.conf import settings
+from django.core.urlresolvers import reverse, NoReverseMatch
from django.core.validators import MaxLengthValidator
from django.db import models
from django.db.models.signals import pre_save
@@ -8,20 +9,31 @@
from tower import ugettext_lazy as _
from challenges.lib import cached_bleach
-from innovate.models import BaseModel
+from innovate.models import BaseModel, BaseModelManager
from projects.models import Project
from users.models import Profile
+class ChallengeManager(BaseModelManager):
+
+ def get_by_natural_key(self, slug):
+ return self.get(slug=slug)
+
+
class Challenge(BaseModel):
"""A user participation challenge on a specific project."""
+ objects = ChallengeManager()
+
title = models.CharField(verbose_name=_(u'Title'), max_length=60, unique=True)
slug = models.SlugField(verbose_name=_(u'Slug'), max_length=60, unique=True)
summary = models.TextField(verbose_name=_(u'Summary'),
validators=[MaxLengthValidator(200)])
description = models.TextField(verbose_name=_(u'Description'))
+ def natural_key(self):
+ return (self.slug,)
+
@property
def description_html(self):
"""Challenge description with bleached HTML."""
@@ -47,15 +59,43 @@ def get_image_src(self):
def __unicode__(self):
return self.title
+
+ def get_absolute_url(self):
+ """Return this challenge's URL.
+
+ Note that this needs to account both for an Ignite-style URL structure,
+ where there is a single challenge for the entire site, and sites where
+ there are multiple challenges.
+
+ """
+ try:
+ # Match for a single-challenge site if we can
+ return reverse('challenge_show')
+ except NoReverseMatch:
+ kwargs = {'project': self.project.slug, 'slug': self.slug}
+ return reverse('challenge_show', kwargs=kwargs)
+
+
+class PhaseManager(BaseModelManager):
+
+ def get_from_natural_key(self, challenge_slug, phase_name):
+ return self.get(challenge__slug=challenge_slug, name=phase_name)
class Phase(BaseModel):
"""A phase of a challenge."""
- challenge = models.ForeignKey(Challenge)
+ objects = PhaseManager()
+
+ challenge = models.ForeignKey(Challenge, related_name='phases')
name = models.CharField(max_length=100)
- # TODO: auto-number phases on save
+ def natural_key(self):
+ return self.challenge.natural_key() + (self.name,)
+
+ natural_key.dependencies = ['challenges.challenge']
+
+ # TODO: replace explicit numbering with start and end dates
order = models.IntegerField()
def __unicode__(self):
@@ -70,8 +110,8 @@ class Submission(BaseModel):
"""A user's entry into a challenge."""
title = models.CharField(verbose_name=_(u'Title'), max_length=60, unique=True)
- brief_description = models.TextField(verbose_name=_(u'Brief Description'),
- validators=[MaxLengthValidator(200)],
+ brief_description = models.CharField(max_length=200,
+ verbose_name=_(u'Brief Description'),
help_text = _(u'Think of this as an elevator pitch - keep it short and sweet'))
description = models.TextField(verbose_name=_(u'Description'))
View
10 apps/challenges/templates/challenges/all.html
@@ -3,7 +3,7 @@
{% from 'layout/helpers.html' import sectiontitle, challenge_nav with context %}
{% block page_title %}
- {{ _('Entries | {part} | Mozilla Labs')|f(part=p11n.title) }}
+ {{ _('Entries | {part} | Mozilla Labs')|f(part=challenge.title) }}
{% endblock %}
{% block page_id %}all_entries{% endblock %}
@@ -14,13 +14,13 @@
(url('innovate_splash'), _('Mozilla Labs Home')),
(url('projects_programs'), _('Programs')),
(url('projects_show', slug=project.slug), project.name),
- (url('challenge_show', project=project.slug, slug=p11n.slug), p11n.title),
+ (challenge.get_absolute_url(), challenge.title),
last=_('Entries')) }}
{% endblock %}
{% block content %}
-{{ sectiontitle(_('All Entries to {part}')|f(part=p11n.title)) }}
-{{ challenge_nav(p11n.get_image_src(), p11n.summary, p11n.start_date, p11n.end_date) }}
+{{ sectiontitle(_('All Entries to {part}')|f(part=challenge.title)) }}
+{{ challenge_nav(challenge.get_image_src(), challenge.summary, challenge.start_date, challenge.end_date) }}
<section class="c4 clearfix">
<div class="row clearfix">
{% if entries %}
@@ -30,7 +30,7 @@
<article>
<h2 class="light sans">{{ e.title }}</h2>
<p>{{ e.brief_description }}</p>
- <p><a href="{{ url('entry_show', project=project.slug, slug=p11n.slug, entry_id=e.id) }}">Read the full entry</a></p>
+ <p><a href="{{ url('entry_show', project=project.slug, slug=challenge.slug, entry_id=e.id) }}">Read the full entry</a></p>
<footer>
<p>Entry from:</p>
<ul class="db_related clearfix">
View
12 apps/challenges/templates/challenges/create.html
@@ -4,13 +4,13 @@
{% block page_title %}
{% if not errors %}
- {{ _('Create entry on {part} : Participation : Mozilla Labs')|f(part=p11n.title) }}
+ {{ _('Create entry on {part} : Participation : Mozilla Labs')|f(part=challenge.title) }}
{% else %}
- {{ _('Entry failed: Create entry on {part} : Participation : Mozilla Labs')|f(part=p11n.title) }}
+ {{ _('Entry failed: Create entry on {part} : Participation : Mozilla Labs')|f(part=challenge.title) }}
{% endif %}
{% endblock %}
-{% block page_id %}p11n_create{% endblock %}
+{% block page_id %}challenge_create{% endblock %}
{% block section_class %}projects{% endblock %}
{% block breadcrumbs %}
@@ -18,12 +18,12 @@
(url('innovate_splash'), _('Mozilla Labs Home')),
(url('projects_programs'), _('Programs')),
(url('projects_show', slug=project.slug), project.name),
- (url('challenge_show', project=project.slug, slug=p11n.slug), p11n.title),
+ (challenge.get_absolute_url(), challenge.title),
last=_('Create entry')) }}
{% endblock %}
{% block content %}
-{{ sectiontitle(_('Create entry on {part}')|f(part=p11n.title)) }}
+{{ sectiontitle(_('Create entry on {part}')|f(part=challenge.title)) }}
<section id="profile_edit" class="w6 clearfix">
<div class="paper c3">
{% if errors %}
@@ -36,7 +36,7 @@
{% endfor %}
</ul>
{% endif %}
- <form action="{{ url('entry_create', project=project.slug, slug=p11n.slug) }}" method="post">
+ <form action="{{ url('entry_create', project=project.slug, slug=challenge.slug) }}" method="post">
{{ csrf()|safe }}
<ul class="db_objects">
<li>
View
14 apps/challenges/templates/challenges/show.html
@@ -3,7 +3,7 @@
{% from "layout/helpers.html" import sectiontitle, challenge_nav with context %}
{% block page_title %}
- {{ _('{part} : Participation : Mozilla Labs')|f(part=p11n.title) }}
+ {{ _('{part} : Participation : Mozilla Labs')|f(part=challenge.title) }}
{% endblock %}
{% block page_id %}participation_detail{% endblock %}
@@ -14,16 +14,16 @@
(url('innovate_splash'), _('Mozilla Labs Home')),
(url('projects_programs'), _('Programs')),
(url('projects_show', slug=project.slug), project.name),
- last=p11n.title) }}
+ last=challenge.title) }}
{% endblock %}
{% block content %}
-{{ sectiontitle(p11n.title) }}
-{{ challenge_nav(p11n.get_image_src(), p11n.summary, p11n.start_date, p11n.end_date) }}
+{{ sectiontitle(challenge.title) }}
+{{ challenge_nav(challenge.get_image_src(), challenge.summary, challenge.start_date, challenge.end_date) }}
<section class="c4 clearfix">
<div class="row clearfix">
<h2 class="light sans">The brief</h2>
- {{ p11n.description }}
+ {{ challenge.description }}
{% if entries %}
<div class="entries">
<h3>Entries</h3>
@@ -33,7 +33,7 @@ <h2 class="light sans">The brief</h2>
<article>
<h4>{{ e.title }}</h4>
<p>{{ e.brief_description }}</p>
- <p><a href="{{ url('entry_show', project=project.slug, slug=p11n.slug, entry_id=e.id) }}">Read the full entry</a></p>
+ <p><a href="{{ url('entry_show', project=project.slug, slug=challenge.slug, entry_id=e.id) }}">Read the full entry</a></p>
<footer>
<p>Entry from:</p>
<ul class="db_related clearfix">
@@ -52,7 +52,7 @@ <h2 class="light sans">The brief</h2>
</ul>
</div>
{% endif %}
- <a href="{{ url('entry_create', project=project.slug, slug=p11n.slug) }}" class="button submit">Participate now</a>
+ <a href="{{ url('entry_create', project=project.slug, slug=challenge.slug) }}" class="button submit">Participate now</a>
</div>
</section>
{% endblock %}
View
8 apps/challenges/templates/challenges/show_entry.html
@@ -3,7 +3,7 @@
{% from 'layout/helpers.html' import sectiontitle, challenge_nav with context %}
{% block page_title %}
- {{ _('{title} | Entries | {part} | Mozilla Labs')|f(title=entry.title, part=p11n.title) }}
+ {{ _('{title} | Entries | {part} | Mozilla Labs')|f(title=entry.title, part=challenge.title) }}
{% endblock %}
{% block page_id %}entry_show{% endblock %}
@@ -14,14 +14,14 @@
(url('innovate_splash'), _('Mozilla Labs Home')),
(url('projects_programs'), _('Programs')),
(url('projects_show', slug=project.slug), project.name),
- (url('challenge_show', project=project.slug, slug=p11n.slug), p11n.title),
- (url('entries_all', project=project.slug, slug=p11n.slug), _('Entries')),
+ (challenge.get_absolute_url(), challenge.title),
+ (url('entries_all', project=project.slug, slug=challenge.slug), _('Entries')),
last=entry.title) }}
{% endblock %}
{% block content %}
{{ sectiontitle(entry.title) }}
-{{ challenge_nav(p11n.get_image_src(), p11n.summary, p11n.start_date, p11n.end_date) }}
+{{ challenge_nav(challenge.get_image_src(), challenge.summary, challenge.start_date, challenge.end_date) }}
<section class="c4 clearfix">
<div class="row clearfix entries">
{{ entry.description_html }}
View
9 apps/challenges/tests/single_challenge_urls.py
@@ -0,0 +1,9 @@
+from django.conf.urls.defaults import patterns, url
+
+urlpatterns = patterns('challenges.views',
+ url(r'$', 'show', name='challenge_show'),
+ url(r'entries/$', 'entries_all', name='entries_all'),
+ url(r'entries/add/$', 'create_entry', name='entry_create'),
+ url(r'entries/(?P<entry_id>\d+)/$', 'entry_show',
+ name='entry_show'),
+ )
View
46 apps/challenges/tests/test_models.py
@@ -1,9 +1,55 @@
+from datetime import datetime, timedelta
+
+from django.conf import settings
from django.test import TestCase
+from mock import Mock, patch
from projects.models import Project
from challenges.models import Challenge, Submission, Phase
+def _create_project_and_challenge():
+ """Create and return a sample project with a sample challenge."""
+ project = Project.objects.create(name='Project', slug='project',
+ allow_participation=True)
+ end_date = datetime.now() + timedelta(days=365)
+ challenge = Challenge.objects.create(title='Challenge',
+ slug='challenge',
+ end_date=end_date,
+ project=project)
+ return project, challenge
+
+
+class PermalinkTest(TestCase):
+
+ def setUp(self):
+ self.project, self.challenge = _create_project_and_challenge()
+
+ def test_permalink(self):
+ self.assertEqual(self.challenge.get_absolute_url(),
+ '/project/challenges/challenge/')
+
+ def tearDown(self):
+ for model in [Challenge, Project]:
+ model.objects.all().delete()
+
+
+class SingleChallengePermalinkTest(TestCase):
+
+ urls = 'challenges.tests.single_challenge_urls'
+
+ def setUp(self):
+ self.project, self.challenge = _create_project_and_challenge()
+
+ def test_single_challenge_permalink(self):
+ """Test permalink generation on an Ignite-style one-challenge site."""
+ self.assertEqual(self.challenge.get_absolute_url(), '/')
+
+ def tearDown(self):
+ for model in [Challenge, Project]:
+ model.objects.all().delete()
+
+
class EntriesToLive(TestCase):
def setUp(self):
View
16 apps/challenges/tests/test_views.py
@@ -139,6 +139,22 @@ def setUp(self):
def tearDown(self):
challenge_teardown()
+ def test_anonymous_form(self):
+ """Check we can't display the entry form without logging in."""
+ response = self.client.get(self.entry_form_path)
+ # Check it's some form of redirect
+ assert response.status_code in xrange(300, 400)
+
+ def test_anonymous_post(self):
+ """Check we can't post an entry without logging in."""
+ form_data = {'title': 'Submission',
+ 'brief_description': 'A submission',
+ 'description': 'A submission of shining wonderment.',
+ 'created_by': User.objects.get(username='alex').id}
+ response = self.client.post(self.entry_form_path, data=form_data)
+ assert response.status_code in xrange(300, 400)
+ assert_equal(Submission.objects.count(), 0)
+
def test_display_form(self):
"""Test the new entry form."""
self.client.login(username='alex', password='alex')
View
28 apps/challenges/views.py
@@ -1,6 +1,7 @@
from django.contrib import messages
+from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
-from django.http import HttpResponseRedirect
+from django.http import HttpResponseRedirect, Http404
from django.shortcuts import get_object_or_404
import jingo
from tower import ugettext as _
@@ -15,8 +16,9 @@ def show(request, project, slug, template_name='challenges/show.html'):
project = get_object_or_404(Project, slug=project)
challenge = get_object_or_404(project.challenge_set, slug=slug)
return jingo.render(request, template_name, {
- 'p11n': challenge,
+ 'challenge': challenge,
'project': project,
+ 'phases': list(enumerate(challenge.phases.all(), start=1)),
'entries': Submission.objects.filter(phase__challenge=challenge),
})
@@ -26,9 +28,16 @@ def entries_all(request, project, slug):
return show(request, project, slug, template_name='challenges/all.html')
+@login_required
def create_entry(request, project, slug):
project = get_object_or_404(Project, slug=project)
- phase = get_object_or_404(Phase, challenge__slug=slug)
+
+ # Quick hack to get around the current inability to obtain current phase
+ try:
+ phase = Phase.objects.filter(challenge__slug=slug)[0]
+ except IndexError:
+ raise Http404
+
profile = request.user.get_profile()
form_errors = False
if request.method == 'POST':
@@ -38,16 +47,11 @@ def create_entry(request, project, slug):
entry.phase = phase
entry.save()
# double save needed to add in m2m key
- entry.created_by = form.cleaned_data['created_by']
- if not profile in form.cleaned_data['created_by']:
- entry.created_by.add(profile)
+ entry.created_by.add(profile)
entry.save()
msg = _('Your entry has been posted successfully and is now available for public review')
messages.success(request, msg)
- return HttpResponseRedirect(reverse('challenge_show', kwargs={
- 'project': project.slug,
- 'slug': slug
- }))
+ return HttpResponseRedirect(phase.challenge.get_absolute_url())
else:
form_errors = {}
# this feels horrible but I think required to create a custom error list
@@ -57,7 +61,7 @@ def create_entry(request, project, slug):
form = EntryForm()
return jingo.render(request, 'challenges/create.html', {
'project': project,
- 'p11n': phase.challenge,
+ 'challenge': phase.challenge,
'form': form,
'errors': form_errors
})
@@ -70,6 +74,6 @@ def entry_show(request, project, slug, entry_id):
phase__challenge=challenge)
return jingo.render(request, 'challenges/show_entry.html', {
'project': project,
- 'p11n': challenge,
+ 'challenge': challenge,
'entry': entry
})
View
2 apps/projects/templates/projects/show.html
@@ -59,7 +59,7 @@ <h3 class="sans light">Participate in this project</h3>
<ul class="db_related clearfix">
{% for p in participation %}
<li>
- <a href="{{ url('challenge_show', project=project.slug, slug=p.slug) }}">
+ <a href="{{ p.get_absolute_url() }}">
<img src="{{ p.get_image_src() }}" alt="" />
<h3>{{ p.title }}</h3>
</a>
View
60 fixtures/ignite.json
@@ -0,0 +1,60 @@
+[
+ {
+ "pk": 1,
+ "model": "projects.project",
+ "fields": {
+ "parent_project_id": null,
+ "description": "Something something gigabit internet",
+ "allow_participation": true,
+ "image": "",
+ "sub_project_label": "",
+ "topics": [],
+ "slug": "us-ignite",
+ "featured": false,
+ "team_members": [],
+ "followers": [],
+ "featured_image": "",
+ "allow_sub_projects": false,
+ "long_description": "Something something gigabit internet.",
+ "name": "US Ignite"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "challenges.challenge",
+ "fields": {
+ "slug": "ignite-challenge",
+ "description": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean sit amet arcu orci. Maecenas scelerisque consequat vehicula. Sed fringilla feugiat nulla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas eros risus, dictum quis dapibus nec, ullamcorper in orci.</p>\r\n\r\n<p>Donec varius, elit eu congue adipiscing, nibh enim viverra urna, euismod rhoncus ante augue sit amet est. Proin convallis facilisis sem, nec auctor elit ultricies eu.</p>",
+ "end_date": "2012-12-31 23:59:59",
+ "title": "Ignite Challenge",
+ "image": "",
+ "summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
+ "project": 1,
+ "allow_voting": false,
+ "moderate": false,
+ "start_date": "2011-11-01 07:59:15"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "challenges.phase",
+ "fields": {
+ "challenge": [
+ "ignite-challenge"
+ ],
+ "name": "Ideation",
+ "order": 1
+ }
+ },
+ {
+ "pk": 3,
+ "model": "challenges.phase",
+ "fields": {
+ "challenge": [
+ "ignite-challenge"
+ ],
+ "name": "Development",
+ "order": 2
+ }
+ }
+]
View
16 settings_ignite.py
@@ -0,0 +1,16 @@
+from settings import *
+
+# ROOT_PACKAGE comes from base settings
+ROOT_URLCONF = '%s.urls_ignite' % ROOT_PACKAGE
+
+IGNITE_PROJECT_SLUG = 'us-ignite'
+IGNITE_CHALLENGE_SLUG = 'ignite-challenge'
+
+FIXTURE_DIRS = (path('fixtures'),)
+
+TEMPLATE_DIRS = (path('templates_ignite'),) + TEMPLATE_DIRS
+
+EXCLUDED_MIDDLEWARE = ('commons.middleware.LocaleURLMiddleware',)
+
+MIDDLEWARE_CLASSES = filter(lambda m: m not in EXCLUDED_MIDDLEWARE,
+ MIDDLEWARE_CLASSES)
View
8 settings_local.py-dist
@@ -1,7 +1,13 @@
# This is an example settings_local.py file.
# Copy it and add your local settings here.
-from settings import *
+# This might be better implemented as a module name, as with Django's
+# DJANGO_SETTINGS_MODULE environment variable, but Python doesn't like
+# importing '*' from a module determined at runtime
+if os.environ.get('DJANGO_SITE') == 'ignite':
+ from settings_ignite import *
+else:
+ from settings import *
DATABASES = {
View
4 templates/layout/helpers.html
@@ -35,12 +35,12 @@ <h1 class="light c6">{{ name }}</h1>
{% macro challenge_nav(image_url, summary, start, finish) %}
<section class="sup c2 clearfix">
{% if entry %}
- <p class="intro">an entry to <a href="{{ url('challenge_show', project=project.slug, slug=p11n.slug) }}">{{ p11n.title }}</a></p>
+ <p class="intro">an entry to <a href="{{ challenge.get_absolute_url() }}">{{ challenge.title }}</a></p>
{% endif %}
<img class="block thick" src="{{ image_url }}" />
<p>Open {{ start|datetime}} until {{ finish|datetime }}</p>
<h2>In summary</h2>
<p>{{ summary }}</p>
- <a href="{{ url('entry_create', project=project.slug, slug=p11n.slug) }}" class="button submit">Participate now</a>
+ <a href="{{ url('entry_create', project=project.slug, slug=challenge.slug) }}" class="button submit">Participate now</a>
</section>
{% endmacro %}
View
71 templates_ignite/challenges/create.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html LANG="{{ LANG }}" dir="{{ DIR }}">
+<head>
+ <title>{% block page_title %}{{ app_name }}{% endblock %}</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <link rel="shortcut icon" type="image/x-icon" href="{{ MEDIA_URL }}img/favicon.png" />
+ {% block site_css %}
+ {% endblock %}
+ <!--[if lt IE 9]>
+ <script type="text/javascript" src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+</head>
+<body id="{% block page_id %}{% endblock %}" class="{% block section_class %}{% endblock %}">
+ <script type="text/javascript">
+ document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/, '');
+ </script>
+ <header>
+ <p>Mozilla + US Ignite</p>
+ <div>
+ <a href="#">Sign in</a>
+ </div>
+ </header>
+ <div role="main">
+ <h1>Create a submission</h1>
+
+ <form action="" method="post">
+ {{ csrf()|safe }}
+ <ul>
+ {{ form.as_ul() }}
+ </ul>
+ {#
+ <ul class="db_objects">
+ <li>
+ <label for="id_title">{{ _('Title') }}</label>
+ {{ form.title }}
+ </li>
+ <li>
+ <label for="id_brief_description">{{ _('Summary') }}</label>
+ <span class="hint">{{ _('Think of this as an elevator pitch. Keep it short and sweet - under 200 characters') }}</span>
+ {{ form.brief_description }}
+ </li>
+ <li>
+ <label for="id_description">{{ _('Description') }}</label>
+ {{ form.description }}
+ </li>
+ <li>
+ <label for="id_created_by">{{ _('Created by') }}</label>
+ <span class="hint">{{ _('Hold down "Control", or "Command" on a Mac, to select more than one. And don\'t forget to add yourself!') }}</span>
+ {{ form.created_by }}
+ </li>
+ </ul>
+ #}
+
+ <button class="submit sans" type="submit">{{ _('Create entry') }}</button>
+ </form>
+ </div>
+ <aside>
+ <section>
+ <h2>Get email updates on all things Mozilla</h2>
+ </section>
+ <section>
+ <h2>Follow and Tweet</h2>
+ </section>
+ <section>
+ <h2>Connect with Mozilla</h2>
+ </section>
+ </aside>
+ {% block site_js %}
+ {% endblock %}
+</body>
+</html>
View
93 templates_ignite/challenges/show.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html LANG="{{ LANG }}" dir="{{ DIR }}">
+<head>
+ <title>{% block page_title %}{{ app_name }}{% endblock %}</title>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <link rel="shortcut icon" type="image/x-icon" href="{{ MEDIA_URL }}img/favicon.png" />
+ {% block site_css %}
+ {% endblock %}
+ <!--[if lt IE 9]>
+ <script type="text/javascript" src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+</head>
+<body id="{% block page_id %}{% endblock %}" class="{% block section_class %}{% endblock %}">
+ <script type="text/javascript">
+ document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/, '');
+ </script>
+ <header>
+ <p>Mozilla + US Ignite</p>
+ <div>
+ <a href="#">Sign in</a>
+ </div>
+ </header>
+ <div role="main">
+ <section>
+ <h1>{{ challenge.title }}</h1>
+ {{ challenge.description_html }}
+ </section>
+
+ <nav id="phases">
+ <ol>
+ {% for phase_number, phase in phases %}
+ <li class="phase">Phase {{ phase_number }}: {{ phase.name }}</li>
+ {% endfor %}
+ </ol>
+ </nav>
+
+ <section class="phase">
+ <header>
+ <h2>Ideation Challenge</h2>
+ <dl>
+ <dt>Start date</dt>
+ <dd>xxxx-xx-xx</dd>
+ <dt>End date</dt>
+ <dd>xxxx-xx-xx</dd>
+ <dt>Time left</dt>
+ <dd>xxx days</dd>
+ </dl>
+ <a href="{{ url('create_entry') }}">Create submission</a>
+ </header>
+ <h3>Submissions</h3>
+ <ul class="submissions">
+ {% for i in range(1, 5) %}
+ <li>
+ <h4>Submission {{ i }}</h4>
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Aenean sit amet arcu orci. Maecenas scelerisque consequat
+ vehicula. Sed fringilla feugiat nulla.</p>
+ </li>
+ {% endfor %}
+ </ul>
+ </section>
+
+ <section id="updates">
+ <h2>Recent updates from the blogs</h2>
+ {% for i in range(3) %}
+ <article>
+ <h3>Headline of post</h3>
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Aenean sit amet arcu orci. Maecenas scelerisque consequat
+ vehicula. Sed fringilla feugiat nulla. Vestibulum ante ipsum
+ primis in faucibus orci luctus et ultrices posuere cubilia
+ Curae; Maecenas eros risus, dictum quis dapibus nec,
+ ullamcorper in orci.</p>
+ </article>
+ {% endfor %}
+ </section>
+
+ </div>
+ <aside>
+ <section>
+ <h2>Get email updates on all things Mozilla</h2>
+ </section>
+ <section>
+ <h2>Follow and Tweet</h2>
+ </section>
+ <section>
+ <h2>Connect with Mozilla</h2>
+ </section>
+ </aside>
+ {% block site_js %}
+ {% endblock %}
+</body>
+</html>
View
35 urls_ignite.py
@@ -0,0 +1,35 @@
+from django.conf import settings
+from django.conf.urls.defaults import patterns, include, url
+from django.contrib import admin
+
+admin.autodiscover()
+
+_ignite_kwargs = {'project': settings.IGNITE_PROJECT_SLUG,
+ 'slug': settings.IGNITE_CHALLENGE_SLUG}
+
+urlpatterns = patterns('',
+ (r'^admin/', include(admin.site.urls)),
+ (r'^browserid/', include('django_browserid.urls')),
+ url(r'^$', 'challenges.views.show', kwargs=_ignite_kwargs, name='challenge_show'),
+ url(r'^entries/add/$', 'challenges.views.create_entry', kwargs=_ignite_kwargs, name='create_entry'),
+)
+
+# Handle 404 and 500 errors
+handler404 = 'innovate.views.handle404'
+handler500 = 'innovate.views.handle500'
+
+## In DEBUG mode, serve media files through Django.
+if settings.DEBUG:
+ # Remove leading and trailing slashes so the regex matches.
+ media_url = settings.MEDIA_URL.lstrip('/').rstrip('/')
+ urlpatterns += patterns('',
+ (r'^%s/(?P<path>.*)$' % media_url, 'django.views.static.serve',
+ {'document_root': settings.MEDIA_ROOT}),
+ )
+
+urlpatterns += patterns('',
+ (r'^mockups/(?P<path>.*)$', 'django.views.static.serve', {
+ 'document_root': 'mockups',
+ 'show_indexes': True,
+ })
+)

0 comments on commit e3c1efb

Please sign in to comment.
Something went wrong with that request. Please try again.