diff options
-rw-r--r-- | accounts/views.py | 5 | ||||
-rw-r--r-- | codemash/settings.py | 1 | ||||
-rw-r--r-- | codemash/urls.py | 1 | ||||
-rw-r--r-- | proposals/__init__.py | 0 | ||||
-rw-r--r-- | proposals/admin.py | 12 | ||||
-rw-r--r-- | proposals/forms.py | 10 | ||||
-rw-r--r-- | proposals/models.py | 75 | ||||
-rw-r--r-- | proposals/templatetags/icons.py | 14 | ||||
-rw-r--r-- | proposals/tests.py | 16 | ||||
-rw-r--r-- | proposals/urls.py | 9 | ||||
-rw-r--r-- | proposals/views.py | 30 | ||||
-rw-r--r-- | templates/accounts/profile.html | 26 | ||||
-rw-r--r-- | templates/proposals/proposal_form.html | 31 |
13 files changed, 226 insertions, 4 deletions
diff --git a/accounts/views.py b/accounts/views.py index aafcfb6..c869085 100644 --- a/accounts/views.py +++ b/accounts/views.py | |||
@@ -2,12 +2,15 @@ from django.shortcuts import render, redirect | |||
2 | from django.contrib.auth import authenticate, login | 2 | from django.contrib.auth import authenticate, login |
3 | from django.contrib.auth.decorators import login_required | 3 | from django.contrib.auth.decorators import login_required |
4 | 4 | ||
5 | from proposals.models import Proposal | ||
5 | from accounts.forms import UserCreationForm | 6 | from accounts.forms import UserCreationForm |
6 | 7 | ||
7 | 8 | ||
8 | @login_required | 9 | @login_required |
9 | def profile(request): | 10 | def profile(request): |
10 | return render(request, "accounts/profile.html") | 11 | return render(request, "accounts/profile.html", { |
12 | 'proposals': Proposal.objects.for_user(request.user), | ||
13 | }) | ||
11 | 14 | ||
12 | 15 | ||
13 | def create_account(request): | 16 | def create_account(request): |
diff --git a/codemash/settings.py b/codemash/settings.py index fbd61f8..efdfb99 100644 --- a/codemash/settings.py +++ b/codemash/settings.py | |||
@@ -129,6 +129,7 @@ INSTALLED_APPS = ( | |||
129 | 129 | ||
130 | 'contact', | 130 | 'contact', |
131 | 'accounts', | 131 | 'accounts', |
132 | 'proposals', | ||
132 | ) | 133 | ) |
133 | 134 | ||
134 | # A sample logging configuration. The only tangible logging | 135 | # A sample logging configuration. The only tangible logging |
diff --git a/codemash/urls.py b/codemash/urls.py index 762679a..da6f216 100644 --- a/codemash/urls.py +++ b/codemash/urls.py | |||
@@ -9,6 +9,7 @@ urlpatterns = patterns('', | |||
9 | (r'^contact/', include('contact.urls', namespace='contact')), | 9 | (r'^contact/', include('contact.urls', namespace='contact')), |
10 | (r'^accounts/', include('django.contrib.auth.urls', namespace='auth')), | 10 | (r'^accounts/', include('django.contrib.auth.urls', namespace='auth')), |
11 | (r'^accounts/', include('accounts.urls', namespace='account')), | 11 | (r'^accounts/', include('accounts.urls', namespace='account')), |
12 | (r'^proposals/', include('proposals.urls', namespace='proposals')), | ||
12 | 13 | ||
13 | url(r'^admin/doc/', include('django.contrib.admindocs.urls')), | 14 | url(r'^admin/doc/', include('django.contrib.admindocs.urls')), |
14 | url(r'^admin/', include(admin.site.urls)), | 15 | url(r'^admin/', include(admin.site.urls)), |
diff --git a/proposals/__init__.py b/proposals/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/proposals/__init__.py | |||
diff --git a/proposals/admin.py b/proposals/admin.py new file mode 100644 index 0000000..5b10ec3 --- /dev/null +++ b/proposals/admin.py | |||
@@ -0,0 +1,12 @@ | |||
1 | from django.contrib import admin | ||
2 | |||
3 | from proposals.models import TimeSlot, Location, TalkType | ||
4 | from proposals.models import AudienceSkillLevel, Category, Proposal | ||
5 | |||
6 | |||
7 | admin.site.register(Category) | ||
8 | admin.site.register(TimeSlot) | ||
9 | admin.site.register(Location) | ||
10 | admin.site.register(Proposal) | ||
11 | admin.site.register(TalkType) | ||
12 | admin.site.register(AudienceSkillLevel) | ||
diff --git a/proposals/forms.py b/proposals/forms.py new file mode 100644 index 0000000..7a32dec --- /dev/null +++ b/proposals/forms.py | |||
@@ -0,0 +1,10 @@ | |||
1 | from django import forms | ||
2 | |||
3 | from proposals.models import Proposal | ||
4 | |||
5 | |||
6 | class UserProposalForm(forms.ModelForm): | ||
7 | |||
8 | class Meta: | ||
9 | model = Proposal | ||
10 | exclude = ("approved", "location", "time_slot") | ||
diff --git a/proposals/models.py b/proposals/models.py new file mode 100644 index 0000000..8551659 --- /dev/null +++ b/proposals/models.py | |||
@@ -0,0 +1,75 @@ | |||
1 | from django.db import models | ||
2 | from django.contrib.auth.models import User | ||
3 | |||
4 | |||
5 | class TimeSlot(models.Model): | ||
6 | |||
7 | slot = models.DateTimeField() | ||
8 | |||
9 | def __str__(self): | ||
10 | return self.slot.strftime("%A, %B %d, %Y @ %I:%M %p") | ||
11 | |||
12 | |||
13 | class Location(models.Model): | ||
14 | |||
15 | name = models.CharField(max_length=255) | ||
16 | |||
17 | def __str__(self): | ||
18 | return self.name | ||
19 | |||
20 | |||
21 | class TalkType(models.Model): | ||
22 | |||
23 | name = models.CharField(max_length=255) | ||
24 | |||
25 | def __str__(self): | ||
26 | return self.name | ||
27 | |||
28 | |||
29 | class AudienceSkillLevel(models.Model): | ||
30 | |||
31 | name = models.CharField(max_length=255) | ||
32 | |||
33 | def __str__(self): | ||
34 | return self.name | ||
35 | |||
36 | |||
37 | class Category(models.Model): | ||
38 | |||
39 | name = models.CharField(max_length=255) | ||
40 | |||
41 | def __str__(self): | ||
42 | return self.name | ||
43 | |||
44 | class Meta: | ||
45 | verbose_name_plural = "Categories" | ||
46 | |||
47 | |||
48 | class ProposalManager(models.Manager): | ||
49 | |||
50 | def for_user(self, user): | ||
51 | return self.get_query_set().filter(proposers=user) | ||
52 | |||
53 | |||
54 | class Proposal(models.Model): | ||
55 | |||
56 | objects = ProposalManager() | ||
57 | |||
58 | title = models.CharField(max_length=255) | ||
59 | |||
60 | type = models.ForeignKey(TalkType) | ||
61 | category = models.ForeignKey(Category) | ||
62 | audience_skill_level = models.ForeignKey(AudienceSkillLevel) | ||
63 | location = models.ForeignKey(Location, blank=True, null=True) | ||
64 | time_slot = models.ForeignKey(TimeSlot, blank=True, null=True) | ||
65 | proposers = models.ManyToManyField(User) | ||
66 | |||
67 | approved = models.BooleanField(default=False) | ||
68 | recording_release = models.BooleanField(default=False) | ||
69 | |||
70 | outline = models.TextField(blank=True) | ||
71 | abstract = models.TextField(blank=True) | ||
72 | notes = models.TextField(blank=True) | ||
73 | |||
74 | def __str__(self): | ||
75 | return self.title | ||
diff --git a/proposals/templatetags/icons.py b/proposals/templatetags/icons.py new file mode 100644 index 0000000..551ad49 --- /dev/null +++ b/proposals/templatetags/icons.py | |||
@@ -0,0 +1,14 @@ | |||
1 | from django import template | ||
2 | from django.utils.html import format_html | ||
3 | from django.contrib.admin.templatetags.admin_static import static | ||
4 | |||
5 | |||
6 | register = template.Library() | ||
7 | |||
8 | |||
9 | @register.filter | ||
10 | def boolean_icon(value): | ||
11 | icon_url = static('admin/img/icon-%s.gif' % | ||
12 | {True: 'yes', False: 'no', None: 'unknown'}[value]) | ||
13 | alt_text = {True: 'yes', False: 'no', None: 'pending' }[value] | ||
14 | return format_html('<img src="{0}" alt="{1}" />', icon_url, alt_text) | ||
diff --git a/proposals/tests.py b/proposals/tests.py new file mode 100644 index 0000000..501deb7 --- /dev/null +++ b/proposals/tests.py | |||
@@ -0,0 +1,16 @@ | |||
1 | """ | ||
2 | This file demonstrates writing tests using the unittest module. These will pass | ||
3 | when you run "manage.py test". | ||
4 | |||
5 | Replace this with more appropriate tests for your application. | ||
6 | """ | ||
7 | |||
8 | from django.test import TestCase | ||
9 | |||
10 | |||
11 | class SimpleTest(TestCase): | ||
12 | def test_basic_addition(self): | ||
13 | """ | ||
14 | Tests that 1 + 1 always equals 2. | ||
15 | """ | ||
16 | self.assertEqual(1 + 1, 2) | ||
diff --git a/proposals/urls.py b/proposals/urls.py new file mode 100644 index 0000000..5a33b6f --- /dev/null +++ b/proposals/urls.py | |||
@@ -0,0 +1,9 @@ | |||
1 | from django.conf.urls import patterns, include, url | ||
2 | |||
3 | from proposals.views import ProposalUpdateView, ProposalCreationForm | ||
4 | |||
5 | |||
6 | urlpatterns = patterns('proposals.views', | ||
7 | url(r'(?P<pk>[0-9]+)$', ProposalUpdateView.as_view(), name='update'), | ||
8 | url(r'create/$', ProposalCreationForm.as_view(), name='create'), | ||
9 | ) | ||
diff --git a/proposals/views.py b/proposals/views.py new file mode 100644 index 0000000..aacba55 --- /dev/null +++ b/proposals/views.py | |||
@@ -0,0 +1,30 @@ | |||
1 | from django.shortcuts import resolve_url | ||
2 | from django.views.generic.edit import CreateView, UpdateView | ||
3 | |||
4 | from proposals.models import Proposal | ||
5 | from proposals.forms import UserProposalForm | ||
6 | |||
7 | |||
8 | class ProposalCreationForm(CreateView): | ||
9 | |||
10 | model = Proposal | ||
11 | form_class = UserProposalForm | ||
12 | |||
13 | def get_success_url(self): | ||
14 | return resolve_url("account:profile") | ||
15 | |||
16 | |||
17 | class ProposalUpdateView(UpdateView): | ||
18 | |||
19 | form_class = UserProposalForm | ||
20 | |||
21 | def get_success_url(self): | ||
22 | return resolve_url("account:profile") | ||
23 | |||
24 | def get_queryset(self): | ||
25 | return Proposal.objects.for_user(self.request.user) | ||
26 | |||
27 | def get_context_data(self, **kwargs): | ||
28 | context = super(ProposalUpdateView, self).get_context_data(**kwargs) | ||
29 | context["edit_view"] = True | ||
30 | return context | ||
diff --git a/templates/accounts/profile.html b/templates/accounts/profile.html index aa6f3ad..fb9e8b1 100644 --- a/templates/accounts/profile.html +++ b/templates/accounts/profile.html | |||
@@ -1,9 +1,29 @@ | |||
1 | {% extends "base.html" %} | 1 | {% extends "base.html" %} |
2 | {% load icons %} | ||
2 | 3 | ||
3 | {% block content %} | 4 | {% block content %} |
4 | <h1>Your Account</h1> | 5 | <h1>Your Account</h1> |
5 | <p>Hello <b>{{ user.email }}</b></p> | 6 | <p>Hello <b>{{ user.email }}</b></p> |
6 | <ul> | 7 | |
7 | <li><a href="{% url 'auth:logout' %}">Logout</a></li> | 8 | <h1>Your Talk Proposals</h1> |
8 | </ul> | 9 | <p><a href="{% url 'proposals:create' %}">Submit a Proposal</a></p> |
10 | |||
11 | {% if proposals %} | ||
12 | <table> | ||
13 | <tr> | ||
14 | <th>Title</th> | ||
15 | <th>Type</th> | ||
16 | <th>Category</th> | ||
17 | <th>Approved</th> | ||
18 | </tr> | ||
19 | {% for proposal in proposals %} | ||
20 | <tr> | ||
21 | <td><a href="{% url 'proposals:update' proposal.id %}">{{ proposal.title }}</a></td> | ||
22 | <td>{{ proposal.type }}</td> | ||
23 | <td>{{ proposal.category }}</td> | ||
24 | <td>{{ proposal.approved|boolean_icon }}</td> | ||
25 | </tr> | ||
26 | {% endfor %} | ||
27 | </table> | ||
28 | {% endif %} | ||
9 | {% endblock %} | 29 | {% endblock %} |
diff --git a/templates/proposals/proposal_form.html b/templates/proposals/proposal_form.html new file mode 100644 index 0000000..79f33bf --- /dev/null +++ b/templates/proposals/proposal_form.html | |||
@@ -0,0 +1,31 @@ | |||
1 | {% extends "base.html" %} | ||
2 | |||
3 | {% block title %}CodeMash Sponsors{% endblock %} | ||
4 | |||
5 | {% block content %} | ||
6 | <h1>{% if edit_view %}Edit{% else %}Submit{% endif %} Proposal</h1> | ||
7 | {% if object.approved %} | ||
8 | <p>Congratulations, your talk has been approved!</p> | ||
9 | |||
10 | <fieldset> | ||
11 | <legend>Scheduling Details</legend> | ||
12 | <p> | ||
13 | <label for="id_time_slot">Time Slot:</label> | ||
14 | <input type="text" id="id_time_slot" value="{{ object.time_slot }}" disabled /> | ||
15 | </p> | ||
16 | <p> | ||
17 | <label for="id_location">Location:</label> | ||
18 | <input type="text" id="id_location" value="{{ object.location }}" disabled /> | ||
19 | </p> | ||
20 | </fieldset> | ||
21 | {% endif %} | ||
22 | |||
23 | <form action="" method="post"> | ||
24 | {% csrf_token %} | ||
25 | <fieldset> | ||
26 | <legend>Talk Details</legend> | ||
27 | {{ form.as_p }} | ||
28 | <input type="submit" value="Save Changes" /> | ||
29 | </fieldset> | ||
30 | </form> | ||
31 | {% endblock %} | ||