Skip to content
Browse files

First commit

  • Loading branch information...
0 parents commit 99155172fb4da8021a364b7494ddca0e5a5ebc9a @jonhull committed Dec 15, 2011
13 app.yaml
@@ -0,0 +1,13 @@
+application: hackerdojo-vote
+version: 1
+runtime: python
+api_version: 1
+
+handlers:
+- url: /favicon.ico
+ static_files: static/favicon.ico
+ upload: static/favicon.ico
+- url: /static
+ static_dir: static
+- url: /.*
+ script: main.py
49 index.yaml
@@ -0,0 +1,49 @@
+indexes:
+
+# AUTOGENERATED
+
+# This index.yaml is automatically updated whenever the dev_appserver
+# detects that a new type of query is run. If you want to manage the
+# index.yaml file manually, remove the above marker line (the line
+# saying "# AUTOGENERATED"). If you want to manage some indexes
+# manually, move them above the marker line. The index.yaml file is
+# automatically uploaded to the admin console when you next deploy
+# your application using appcfg.py.
+
+- kind: Issue
+ properties:
+ - name: created_by
+ - name: creation_date
+
+- kind: Issue
+ properties:
+ - name: creator
+ - name: creation_date
+
+- kind: Issue
+ properties:
+ - name: creator
+ - name: creation_date
+ direction: desc
+
+- kind: Issue
+ properties:
+ - name: member
+ - name: creation_date
+
+- kind: Issue
+ properties:
+ - name: status
+ - name: end_time
+ direction: desc
+
+- kind: Vote
+ properties:
+ - name: member
+ - name: update_time
+
+- kind: Vote
+ properties:
+ - name: member
+ - name: update_time
+ direction: desc
178 main.py
@@ -0,0 +1,178 @@
+import cgi
+from google.appengine.ext import webapp, db
+from google.appengine.ext.webapp import util, template
+from google.appengine.api import urlfetch, memcache, users, mail
+
+import logging, urllib, os
+from datetime import datetime, timedelta
+
+from models import Issue, Choice, Vote
+
+
+class MainPage(webapp.RequestHandler):
+ def get(self):
+ user = users.get_current_user()
+ if user:
+ logout_url = users.create_logout_url('/')
+ else:
+ login_url = users.create_login_url('/')
+ issues = Issue.all().order('creation_date').fetch(30)
+ success_type = self.request.get('success')
+ success_msg = None
+ if success_type == 'vote':
+ success_msg = 'Your vote was successfully cast!'
+ if success_type == 'updated':
+ success_msg = 'Your vote was successfully updated!'
+ created_by = Issue.issues_created_by(member=user,limit=20)
+ voted_on = Issue.issues_voted_on(member=user,limit=20)
+ #recent_results = [issue for issue in voted_on if issue.has_results]
+ recent_voted = [issue for issue in voted_on if issue.is_active()]
+ recent_results = Issue.recent_results(limit=20)
+ self.response.out.write(template.render('templates/overview.html', locals()))
+
+
+
+class NewHandler(webapp.RequestHandler):
+ def get(self):
+ user = users.get_current_user()
+ if user:
+ logout_url = users.create_logout_url('/')
+ else:
+ self.redirect(users.create_login_url(self.request.uri))
+ return
+ option_one = "Yes"
+ option_two = "No"
+ self.response.out.write(template.render('templates/new.html', locals()))
+
+ def post(self):
+ user = users.get_current_user()
+ if not user:
+ self.redirect(users.create_login_url(self.request.uri))
+ return
+
+ duration_amount = int(self.request.get('duration_amount'))
+ multiplier = int(self.request.get('duration_multiplier'))
+ issue = Issue(
+ title = cgi.escape(self.request.get('title')),
+ description = cgi.escape(self.request.get('description')),
+ duration = duration_amount * multiplier,
+ )
+ issue.put()
+ if self.request.get('option1'):
+ issue.add_choice(cgi.escape(self.request.get('option1')))
+ if self.request.get('option2'):
+ issue.add_choice(cgi.escape(self.request.get('option2')))
+ if self.request.get('option3'):
+ issue.add_choice(cgi.escape(self.request.get('option3')))
+ if self.request.get('option4'):
+ issue.add_choice(cgi.escape(self.request.get('option4')))
+ if self.request.get('option5'):
+ issue.add_choice(cgi.escape(self.request.get('option5')))
+
+ self.redirect('/issue/%s' % (issue.key().id()))
+
+
+
+class EditHandler(webapp.RequestHandler):
+ def get(self,id):
+ user = users.get_current_user()
+ if user:
+ logout_url = users.create_logout_url('/')
+ else:
+ self.redirect(users.create_login_url(self.request.uri))
+ return
+ issue = Issue.get_by_id(int(id))
+ choices = issue.choices
+ self.response.out.write(template.render('templates/edit.html', locals()))
+
+ def post(self,id):
+ user = users.get_current_user()
+ if user:
+ logout_url = users.create_logout_url('/')
+ else:
+ self.redirect(users.create_login_url(self.request.uri))
+ return
+ issue = Issue.get_by_id(int(id))
+
+
+ if self.request.get('extend'):#if extending vote
+ choices = issue.choices
+ extend_amount = int(self.request.get('extend_amount')) * int(self.request.get('extend_multiplier'))
+ issue.extend_duration(extend_amount)
+ self.response.out.write(template.render('templates/edit.html', locals()))
+
+ else:#otherwise we are saving changes
+ duration_amount = int(self.request.get('duration_amount'))
+ multiplier = int(self.request.get('duration_multiplier'))
+ issue.duration = duration_amount * multiplier
+ if self.request.get('title'):
+ issue.title = cgi.escape(self.request.get('title'))
+ if self.request.get('description'):
+ issue.description = cgi.escape(self.request.get('description'))
+ if self.request.get('option1') and self.request.get('option2'):
+ choices = issue.choices
+ db.delete(choices)
+ issue.add_choice(cgi.escape(self.request.get('option1')))
+ issue.add_choice(cgi.escape(self.request.get('option2')))
+ if self.request.get('option3'):
+ issue.add_choice(cgi.escape(self.request.get('option3')))
+ if self.request.get('option4'):
+ issue.add_choice(cgi.escape(self.request.get('option4')))
+ if self.request.get('option5'):
+ issue.add_choice(cgi.escape(self.request.get('option5')))
+ issue.put()
+ #choices = issue.choices
+ self.redirect('/issue/%s' % (id))
+ #self.response.out.write(template.render('templates/edit.html', locals()))
+
+
+
+class IssueHandler(webapp.RequestHandler):
+ def get(self,id):
+ user = users.get_current_user()
+ if user:
+ logout_url = users.create_logout_url('/')
+ else:
+ self.redirect(users.create_login_url(self.request.uri))
+ return
+
+ issue = Issue.get_by_id(int(id))
+ issue.update_status()
+
+ vote = issue.vote_for_member(user)
+
+ issueUrl = self.request.uri
+ self.response.out.write(template.render('templates/Issue.html', locals()))
+
+
+ def post(self,id):
+ user = users.get_current_user()
+ if not user: #don't want someone who is not authenticated to be able to vote
+ self.redirect(users.create_login_url(self.request.uri))
+ return
+
+ issue = Issue.get_by_id(int(id))
+ #vote = issue.vote_for_member()
+
+ new_choice = Choice.get_by_id(int(self.request.get('choice')))
+ was_updated = issue.register_vote(new_choice)
+
+ if was_updated:
+ self.redirect('/?success=updated')
+ else:
+ self.redirect('/?success=vote')
+
+
+
+
+def main():
+ application = webapp.WSGIApplication([
+ ('/',MainPage),
+ ('/new',NewHandler),
+ ('/issue/(\d+).*',IssueHandler),
+ ('/edit/(\d+).*',EditHandler)],
+ debug=True)
+ util.run_wsgi_app(application)
+
+if __name__ == '__main__':
+ main()
172 models.py
@@ -0,0 +1,172 @@
+from google.appengine.ext import db
+from google.appengine.api import urlfetch, memcache, users, mail
+from datetime import datetime, timedelta
+
+import logging
+
+
+class Issue(db.Model):
+ """Represents a single issue which is being voted on"""
+ status = db.StringProperty(required=True, default='active', choices=set(
+ ['active', 'done', 'canceled']))
+ is_public = db.BooleanProperty(default=False) #is issue listed on site or just by sharing url?
+ creator = db.UserProperty(auto_current_user=True)
+ title = db.StringProperty(required=True)
+ description = db.TextProperty()
+ duration = db.IntegerProperty()
+ creation_date = db.DateTimeProperty(auto_now_add=True)
+ start_time = db.DateTimeProperty() #time when first vote is cast
+ end_time = db.DateTimeProperty() #time when vote will end
+
+ #Implicit Properties:
+ #choices = Implicitly created list of choice objects
+ #votes - implicitly created list of vote objects
+
+ def add_choice(self,choice_name):
+ new_choice = Choice(name=choice_name,issue=self)
+ new_choice.put()
+
+ def remove_choice(self,choice):
+ choice.delete()
+
+ def vote_count(self):
+ return self.votes.count(999999)
+
+ def vote_for_member(self,member=None):
+ if not member:
+ member = users.get_current_user()
+ logging.info('member:%s voted:%s' % (member.nickname(),self.votes.filter('member =',member).fetch(20)))
+ return self.votes.filter('member =',member).get()
+
+ def register_vote(self,choice,member=None):
+ if not member:
+ member = users.get_current_user()
+ member_vote = self.vote_for_member(member)
+ was_changed = False
+ if(member_vote):
+ member_vote.choice = choice
+ was_changed = True
+ else:
+ member_vote = Vote(member=member,choice=choice,issue=self)
+ member_vote.put()
+ if(not self.start_time):
+ self.start_time = datetime.now()
+ self.end_time = self.start_time + timedelta(hours=self.duration)
+ self.put()
+ return was_changed
+
+ def extend_duration(self,hours):
+ self.duration += hours
+ if self.start_time:
+ self.end_time = self.start_time + timedelta(hours=self.duration)
+ self.put()
+
+ def days_left(self):
+ delta = self.end_time - datetime.now()
+ return delta.days
+
+ def hours_left(self):
+ delta = self.end_time - datetime.now()
+ #days = delta.days
+ #hours = days*24 + delta.seconds/3600
+ hours = delta.seconds/3600
+ return hours
+
+ def is_active(self):
+ return self.status in ('active')
+
+ def has_results(self):
+ return self.status in ('done')
+
+ def member_is_creator(self,member=None):
+ if not member:
+ member = users.get_current_user()
+ return member == self.creator
+
+ def winning_choices(self):#returns list of keys of winning choices (may be a tie)
+ result = []
+ high_vote = 0
+ for choice in self.choices:
+ cnt = choice.vote_count()
+ if cnt == high_vote:
+ result.append(choice.key())
+ elif cnt > high_vote:
+ result = [choice.key()]
+ high_vote = cnt
+ return result
+
+ def update_status(self):
+ if self.is_active:
+ if self.end_time:
+ if self.end_time <= datetime.now():
+ logging.info('status changed for issue: %s' % (self.title))
+ self.status = 'done'
+ self.put()
+
+ @classmethod
+ def issues_created_by(cls, member=None,limit=20):
+ if not member:
+ member = users.get_current_user()
+ return cls.all().filter('creator =',member).order('-creation_date').fetch(limit)
+
+ @classmethod
+ def issues_voted_on(cls, member=None, limit=20):
+ if not member:
+ member = users.get_current_user()
+ if not member:#if logged out
+ return []
+ member_votes = Vote.all().filter('member =',member).order('-update_time').fetch(limit)
+ ##logging.info('member_votes:%s' % (member_votes))
+ ##logging.info('output:%s' % ([vote.issue for vote in member_votes]))
+ return [vote.issue for vote in member_votes]
+
+ @classmethod
+ def recent_results(cls, member=None,limit=20):#*** Need to fix, limit will be incorrect here because of filtering
+ if not member:
+ member = users.get_current_user()
+ if not member:#if logged out
+ return []
+ recent = cls.all().filter('status =','done').order('-end_time').fetch(limit)
+ return [issue for issue in recent if issue.vote_for_member()] #***this is probably slow
+ #member_votes = Vote.all().filter('member =',member).fetch(limit)
+ #return [vote.issue for vote in member_votes if vote.issue.has_results()]
+
+
+
+class Choice(db.Model):
+ """Represents a possible response to an issue (e.g. Yes)"""
+ name = db.StringProperty(required=True)
+ issue = db.ReferenceProperty(Issue,collection_name='choices')
+ #votes - implicitly created list of vote objects
+
+ def is_member_vote(self,member=None):
+ if not member:
+ member = users.get_current_user()
+ if self.votes.filter('member =',member).get():
+ logging.info("Yup member:%s (%s)" % (member.nickname(),users.get_current_user()))
+ return True
+ logging.info("Nope")
+ return False
+
+ def vote_count(self):
+ return self.votes.count(999999)
+
+ def percentage(self):
+ total_votes = float(self.issue.vote_count())
+ if(total_votes):
+ return round(float(self.vote_count()) / total_votes,3) * 100 #cnt/total
+ return 0
+
+ def is_winning(self):
+ logging.info('%s in result(s) %s: %s',self.name,self.issue.winning_choices()[0].name, self.issue.winning_choices())
+ return self.key() in self.issue.winning_choices()
+
+
+
+
+class Vote(db.Model):
+ """Represents a single vote by a member"""
+ member = db.UserProperty(auto_current_user=True)
+ choice = db.ReferenceProperty(Choice, collection_name='votes')
+ issue = db.ReferenceProperty(Issue,collection_name='votes')
+ update_time = db.DateTimeProperty(auto_now=True)
BIN models.pyc
Binary file not shown.
BIN static/.DS_Store
Binary file not shown.
BIN static/dojo_icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN static/favicon.ico
Binary file not shown.
BIN templates/.DS_Store
Binary file not shown.
28 templates/base.html
@@ -0,0 +1,28 @@
+<html>
+ <head>
+ <title>Hacker Dojo Member Vote</title>
+ {% block feed %}
+ <link rel="alternate" type="application/xml" title="Hacker Dojo Member" href="/events.rss" />
+ {% endblock %}
+
+ </head>
+ <body>
+ <div id="top">
+ {% if user %}
+ <span><strong>{{user.email}}</strong> | <a href="/myvotes">My Votes</a> | <a href="{{logout_url}}">Logout</a></span>
+ {% else %}
+ <span><a style="font-weight: bold;" href="{{login_url}}">Login</a> | <a href="http://signup.hackerdojo.com/upgrade/needaccount">Need an account?</a></span>
+ {% endif %}
+ </div>
+ <div id="wrapper">
+ <div id="header">
+ <img src="/static/dojo_icon.png" style="float: left;" />
+ <h1>Member Vote</h1>
+ </div>
+ <div id="content">
+ {% block content %}{% endblock %}
+ </div>
+ </div>
+
+ </body>
+</html>
117 templates/edit.html
@@ -0,0 +1,117 @@
+{% extends 'base.html' %}
+{% block content %}
+
+<h1>Edit Issue:{{issue.title}}</h1>
+
+<form method="post">
+
+ {% if issue.vote_count %}
+ <div><b>Unable to change issue text once votes have been cast</b></div>
+ Issue Title:
+ <div>{{issue.title}}</div>
+
+ Issue:
+ <div>{{issue.description}}</textarea></div>
+
+ Options:
+ {% for choice in issue.choices %}
+ <div>{{choice.name}}</div>
+ {% endfor %}
+ <div><input type="checkbox" name="allow_add" DISABLED/>Allow members to add choices (coming soon)</div>
+
+ Duration:
+ <div>
+ {{issue.vote_count}} members have voted on this issue so far.
+ {% if issue.days_left %}
+ <b>{{issue.days_left}} days {{issue.hours_left}} hours</b> remaining before the vote closes.
+ {% else %}
+ {% if issue.hours_left %}
+ <b>{{issue.hours_left}} hours</b> remaining before the vote closes.
+ {% else %}
+ <b>Less than an hour</b> remaining before the vote closes.
+ {% endif %}
+ {% endif %}
+ </div>
+ </br>
+ <div>Extend voting time by
+ <select name="extend_amount">
+ <option value="1">1</option>
+ <option value="2">2</option>
+ <option value="3">3</option>
+ <option value="4">4</option>
+ <option value="5">5</option>
+ <option value="6">6</option>
+ <option value="7">7</option>
+ <option value="8">8</option>
+ <option value="9">9</option>
+ <option value="10">10</option>
+ <option value="11">11</option>
+ <option value="12">12</option>
+ </select>
+ <select name="extend_multiplier">
+ <option value="1" selected>hour(s)</option>
+ <option value="24">day(s)</option>
+ <option value="168">week(s)</option>
+ </select>
+ <input type="submit" value="Extend Vote" name="extend">
+ </div>
+ {% else %}
+ <div><b>You will be unable to change issue text once votes have been cast</b></div>
+ Issue Title:
+ <div><input type="text" name="title" value="{{issue.title}}" size="60"/></div>
+ Issue:
+ <div><textarea name="description" rows="5" cols="44"/>{{issue.description}}</textarea></div>
+ Options:
+ <div><input type="text" name="option1" size="60" value="{{choices.0.name}}"/></div>
+ <div><input type="text" name="option2" size="60" value="{{choices.1.name}}"/></div>
+ <div><input type="text" name="option3" size="60" value="{{choices.2.name}}"/></div>
+ <div><input type="text" name="option4" size="60" value="{{choices.3.name}}"/></div>
+ <div><input type="text" name="option5" size="60" value="{{choices.4.name}}" /></div>
+ <div><input type="checkbox" name="allow_add"/>Allow members to add choices (coming soon)</div>
+ Duration:
+ <div><select name="duration_amount">
+ <option value="1">1</option>
+ <option value="2">2</option>
+ <option value="3">3</option>
+ <option value="4">4</option>
+ <option value="5">5</option>
+ <option value="6">6</option>
+ <option value="7">7</option>
+ <option value="8">8</option>
+ <option value="9">9</option>
+ <option value="10">10</option>
+ <option value="11">11</option>
+ <option value="12">12</option>
+ </select>
+ <select name="duration_multiplier">
+ <option value="1">hour(s)</option>
+ <option value="24" selected>day(s)</option>
+ <option value="168">week(s)</option>
+ </select>
+ from
+ <select name="duration_start">
+ <option>the first vote</option>
+ </select>
+ </div>
+ Visibility: (coming soon)
+ <div><select>
+ <option selected>Private (email link only)</option>
+ <option>All members</option>
+ <optgroup label="Member Group">
+ <option>Events</option>
+ <option>Operations</option>
+ <option>Other team</option>
+ </optgroup>
+ <optgroup label="Private Group">
+ <option>Directors</option>
+ <option>Other Private Group</option>
+ </optgroup>
+ </select>
+ </div>
+ </br>
+ <div><input type="submit" value="Save Changes" name="save_changes"></div>
+ {% endif %}
+
+</form>
+
+{% endblock %}
71 templates/issue.html
@@ -0,0 +1,71 @@
+{% extends 'base.html' %}
+{% block content %}
+
+<div id="primary">
+
+ {% if issue.member_is_creator %}
+ This issue is currently private. You may share it by copy and pasting the following url into an email: <b>{{issueUrl}}</b>
+ {% if issue.is_active %}
+ <form method="get" action="/edit/{{issue.key.id}}"><input type = "submit" value="edit"/></form>
+ <form method="post">
+ <input type = "submit" value="stop voting early"/>(coming soon)
+ </form>
+ {% endif %}
+ {% endif %}
+
+ <h3>{{issue.title|title}}</h3>
+
+ <div>{{issue.description}}</div>
+ </br>
+
+ {% if issue.is_active %}
+ {% if vote %}Your current vote is <b>{{vote.choice.name}}</b>{% endif %}
+ <form method="post">
+ {% for choice in issue.choices %}
+ <input type = "radio" name="choice" value="{{choice.key.id}}" {% if choice.is_member_vote %} checked {% endif %} /> {{choice.name}}</br>
+ {% endfor %}
+ {% if vote %}
+ <input type = "submit" value="Change Vote"/>
+ {% else %}
+ <input type = "submit" value="Vote!"/>
+ {% endif %}
+ </form>
+ {% if issue.vote_count %}
+ {{issue.vote_count}} members have voted on this issue so far.
+ {% if issue.days_left %}
+ <b>{{issue.days_left}} days {{issue.hours_left}} hours</b> remaining before the vote closes.
+ {% else %}
+ {% if issue.hours_left %}
+ <b>{{issue.hours_left}} hours</b> remaining before the vote closes.
+ {% else %}
+ <b>Less than an hour</b> remaining before the vote closes.
+ {% endif %}
+ {% endif %}
+ {% else %}
+ No one has voted on this issue yet. you can be the first! Vote will last <b>{{issue.duration}} hours</b> after the first vote
+ {% endif %}
+
+ {% else %}
+ {% if issue.has_results %}
+ <h3>Results:</h3>
+ {% for choice in issue.choices %}
+ {% if choice.is_winning %}
+ <b>{{choice.name}}: {{choice.vote_count}} votes ({{choice.percentage}}%)</b> </br>
+ {% else %}
+ {{choice.name}}: {{choice.vote_count}} votes ({{choice.percentage}}%) </br>
+ {% endif %}
+ {% endfor %}
+ </br>Total: {{issue.vote_count}} votes </br>
+
+ </br>Voting Members:</br>
+ {% for vote in issue.votes %}
+ <div>{{vote.member.nickname}}</div>
+ {% endfor %}
+ {% endif %}
+ {% endif %}
+
+
+</div>
+
+
+{% endblock %}
62 templates/new.html
@@ -0,0 +1,62 @@
+{% extends 'base.html' %}
+{% block content %}
+
+<h1>New Issue</h1>
+
+<form method="post">
+ Issue Title:
+ <div><input type="text" name="title" size="60"/></div>
+ Issue:
+ <div><textarea name="description" rows="5" cols="44"/></textarea></div>
+ Options:
+ <div><input type="text" name="option1" size="60" value="{{option_one}}"/></div>
+ <div><input type="text" name="option2" size="60" value="{{option_two}}"/></div>
+ <div><input type="text" name="option3" size="60"/></div>
+ <div><input type="text" name="option4" size="60"/></div>
+ <div><input type="text" name="option5" size="60"/></div>
+ <div><input type="checkbox" name="allow_add"/>Allow members to add choices (coming soon)</div>
+ Duration:
+ <div><select name="duration_amount">
+ <option value="1">1</option>
+ <option value="2">2</option>
+ <option value="3">3</option>
+ <option value="4">4</option>
+ <option value="5">5</option>
+ <option value="6">6</option>
+ <option value="7">7</option>
+ <option value="8">8</option>
+ <option value="9">9</option>
+ <option value="10">10</option>
+ <option value="11">11</option>
+ <option value="12">12</option>
+ </select>
+ <select name="duration_multiplier">
+ <option value="1">hour(s)</option>
+ <option value="24" selected>day(s)</option>
+ <option value="168">week(s)</option>
+ </select>
+ from
+ <select name="duration_start">
+ <option>the first vote</option>
+ </select>
+ </div>
+ Visibility: (coming soon)
+ <div><select>
+ <option selected>Private (email link only)</option>
+ <option>All members</option>
+ <optgroup label="Member Group">
+ <option>Events</option>
+ <option>Operations</option>
+ <option>Other team</option>
+ </optgroup>
+ <optgroup label="Private Group">
+ <option>Directors</option>
+ <option>Other Private Group</option>
+ </optgroup>
+ </select>
+ </div>
+ </br>
+ <div><input type="submit" value="Create Issue"></div>
+</form>
+
+{% endblock %}
42 templates/overview.html
@@ -0,0 +1,42 @@
+{% extends 'base.html' %}
+{% block content %}
+
+<form action="/new" method="get">
+ <div><input type="submit" value="New Issue"></div>
+</form>
+
+{% if success_msg %}
+ <div class="success_msg"><b>{{success_msg}}</b></div>
+{% endif %}
+
+<h3>Current Issues</h3>
+{% if user %}
+ {% for issue in issues %}
+ <a href="/issue/{{issue.key.id}}">{{issue.title}}</a></br>
+ {% endfor %}
+{% else %}
+ <b>You must log in to vote</b>
+{% endif %}
+
+<h3>Recent Results</h3>
+{% for issue in recent_results %}
+ <a href="/issue/{{issue.key.id}}">{{issue.title}}</a></br>
+{% endfor %}
+
+<h3>Recent Votes</h3>
+{% for issue in recent_voted %}
+ <a href="/issue/{{issue.key.id}}">{{issue.title}}</a></br>
+{% endfor %}
+
+{% if user %}
+ <h3>My Issues</h3>
+ {% if created_by %}
+ {% for issue in created_by %}
+ <a href="/issue/{{issue.key.id}}">{{issue.title}}</a></br>
+ {% endfor %}
+ {% else %}
+ You have not created any issues yet
+ {% endif %}
+{% endif %}
+
+{% endblock %}

0 comments on commit 9915517

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