diff --git a/alembic/env.py b/alembic/env.py
index bad6e54..36958d9 100644
--- a/alembic/env.py
+++ b/alembic/env.py
@@ -76,4 +76,3 @@ def run_migrations_online():
run_migrations_offline()
else:
run_migrations_online()
-
diff --git a/alembic/versions/29c2cf7cd05f_add_comments_to_even.py b/alembic/versions/29c2cf7cd05f_add_comments_to_even.py
new file mode 100644
index 0000000..8e45bfe
--- /dev/null
+++ b/alembic/versions/29c2cf7cd05f_add_comments_to_even.py
@@ -0,0 +1,22 @@
+"""Add comments to events
+
+Revision ID: 29c2cf7cd05f
+Revises: 2e8783dd05
+Create Date: 2013-04-19 21:29:32.485229
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '29c2cf7cd05f'
+down_revision = '2e8783dd05'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('event', sa.Column('comments_id', sa.Integer, sa.ForeigenKey('commentspace.id')))
+
+
+def downgrade():
+ op.remove_column('event', 'comments_id')
diff --git a/hacknight/models/__init__.py b/hacknight/models/__init__.py
index daebcb6..e720907 100644
--- a/hacknight/models/__init__.py
+++ b/hacknight/models/__init__.py
@@ -6,9 +6,11 @@
db = SQLAlchemy(app)
-from hacknight.models.user import *
from hacknight.models.event import *
+from hacknight.models.user import *
+from hacknight.models.profile import *
from hacknight.models.venue import *
from hacknight.models.project import *
from hacknight.models.participant import *
from hacknight.models.sponsor import *
+from hacknight.models.comment import *
diff --git a/hacknight/models/comment.py b/hacknight/models/comment.py
index fb0e744..748b41a 100644
--- a/hacknight/models/comment.py
+++ b/hacknight/models/comment.py
@@ -1,11 +1,9 @@
# -*- coding: utf-8- *-
-from hacknight.models import BaseMixin, BaseScopedIdNameMixin, BaseScopedIdMixin
+from hacknight.models import BaseMixin, BaseScopedIdMixin
from hacknight.models import db
-from hacknight.models.event import Event
-from hacknight.models.participant import Participant
from hacknight.models.user import User
-from hacknight.models.vote import Vote, VoteSpace
+from hacknight.models.vote import VoteSpace
__all__ = ['CommentSpace', 'Comment']
diff --git a/hacknight/models/event.py b/hacknight/models/event.py
index 8441d44..afb3b04 100644
--- a/hacknight/models/event.py
+++ b/hacknight/models/event.py
@@ -3,9 +3,18 @@
from flask import url_for
from flask.ext.lastuser.sqlalchemy import ProfileMixin
from sqlalchemy.orm import deferred
+<<<<<<< HEAD
+from hacknight.models import db, BaseScopedNameMixin
+from hacknight.models.profile import Profile
+from hacknight.models.comment import CommentSpace
+
+
+__all__ = ['Event', 'EVENT_STATUS']
+=======
from hacknight.models import db, BaseNameMixin, BaseScopedNameMixin, BaseMixin
__all__ = ['Profile', 'Event', 'EVENT_STATUS', 'PROFILE_TYPE', 'EventRedirect']
+>>>>>>> master
#need to add EventTurnOut, EventPayment later
@@ -34,6 +43,8 @@ class EVENT_STATUS:
WITHDRAWN = 7
+<<<<<<< HEAD
+=======
class Profile(ProfileMixin, BaseNameMixin, db.Model):
__tablename__ = 'profile'
@@ -51,8 +62,10 @@ def url_for(self, action='view', _external=True):
return url_for('event_new', profile=self.name, _external=_external)
+>>>>>>> master
class Event(BaseScopedNameMixin, db.Model):
__tablename__ = 'event'
+
profile_id = db.Column(db.Integer, db.ForeignKey('profile.id'), nullable=False)
profile = db.relationship(Profile)
parent = db.synonym('profile')
@@ -76,8 +89,17 @@ class Event(BaseScopedNameMixin, db.Model):
pending_message = deferred(db.Column(db.UnicodeText, nullable=False, default=u''))
pending_message_text = deferred(db.Column(db.UnicodeText, nullable=False, default=u''))
+ #event wall
+ comments_id = db.Column(db.Integer, db.ForeignKey('commentspace.id'), nullable=False)
+ comments = db.relationship(CommentSpace, uselist=False)
+
__table_args__ = (db.UniqueConstraint('name', 'profile_id'),)
+ def __init__(self, **kwargs):
+ super(Event, self).__init__(**kwargs)
+ if not self.comments:
+ self.comments = CommentSpace()
+
def owner_is(self, user):
"""Check if a user is an owner of this event"""
return user is not None and self.profile.userid in user.user_organizations_owned_ids()
diff --git a/hacknight/models/profile.py b/hacknight/models/profile.py
new file mode 100644
index 0000000..31dc04a
--- /dev/null
+++ b/hacknight/models/profile.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+from flask import url_for
+from hacknight.models import db, BaseNameMixin
+
+__all__ = ['Profile', 'PROFILE_TYPE']
+
+
+class PROFILE_TYPE:
+ UNDEFINED = 0
+ PERSON = 1
+ ORGANIZATION = 2
+ EVENTSERIES = 3
+
+profile_types = {
+ 0: u"Undefined",
+ 1: u"Person",
+ 2: u"Organization",
+ 3: u"Event Series",
+ }
+
+
+class Profile(BaseNameMixin, db.Model):
+ __tablename__ = 'profile'
+
+ userid = db.Column(db.Unicode(22), nullable=False, unique=True)
+ description = db.Column(db.UnicodeText, default=u'', nullable=False)
+ type = db.Column(db.Integer, default=PROFILE_TYPE.UNDEFINED, nullable=False)
+
+ def type_label(self):
+ return profile_types.get(self.type, profile_types[0])
+
+ def url_for(self, action='view', _external=True):
+ if action == 'view':
+ return url_for('profile_view', profile=self.name, _external=_external)
+ elif action == 'new-event':
+ return url_for('event_new', profile=self.name, _external=_external)
diff --git a/hacknight/models/user.py b/hacknight/models/user.py
index b757a22..dd115c6 100644
--- a/hacknight/models/user.py
+++ b/hacknight/models/user.py
@@ -3,7 +3,7 @@
from flask import url_for
from flask.ext.lastuser.sqlalchemy import UserBase
from hacknight.models import db
-from hacknight.models.event import Profile
+from hacknight.models.profile import Profile
__all__ = ['User']
diff --git a/hacknight/models/venue.py b/hacknight/models/venue.py
index af1ae74..2a4a2e6 100644
--- a/hacknight/models/venue.py
+++ b/hacknight/models/venue.py
@@ -3,7 +3,7 @@
from flask import url_for
from hacknight.models import BaseNameMixin
from hacknight.models import db
-from hacknight.models.event import Profile
+from hacknight.models.profile import Profile
__all__ = ['Venue']
diff --git a/hacknight/models/vote.py b/hacknight/models/vote.py
index b5eea3e..fcaf73a 100644
--- a/hacknight/models/vote.py
+++ b/hacknight/models/vote.py
@@ -1,13 +1,13 @@
# -*- coding: utf-8- *-
-from hacknight.models import BaseMixin, BaseScopedIdNameMixin
+from hacknight.models import BaseMixin
from hacknight.models import db
-from hacknight.models.event import Event
-from hacknight.models.participant import Participant
from hacknight.models.user import User
+
__all__ = ['VoteSpace', 'Vote']
+
class VoteSpace(BaseMixin, db.Model):
__tablename__ = 'votespace'
type = db.Column(db.Integer, nullable=True)
diff --git a/hacknight/templates/comment_owner_email.md b/hacknight/templates/comment_owner_email.md
index 6d0a33a..407eab5 100644
--- a/hacknight/templates/comment_owner_email.md
+++ b/hacknight/templates/comment_owner_email.md
@@ -1,4 +1,4 @@
-**{{ g.user.username }}** replied to you in the project **{{ project.title }}**
+**{{ g.user.username }}** replied to you {% if wall %} on the event wall {% else %} in the project {% endif %} **{{ project.title }}**
{{ comment.message }}
diff --git a/hacknight/templates/event.html b/hacknight/templates/event.html
index dab0a3e..de63acf 100644
--- a/hacknight/templates/event.html
+++ b/hacknight/templates/event.html
@@ -1,4 +1,7 @@
{% extends "layout.html" %}
+{% from "comments.html" import commenttree %}
+{% from "forms.html" import renderform, ajaxform %}
+
{% block title %}{{ event.title }}{% endblock %}
{% macro participant_list(event, participants) -%}
@@ -142,6 +145,7 @@
@@ -192,6 +196,55 @@
New project...
+
{% if event.venue.latitude and event.venue.longitude %}
@@ -235,7 +288,8 @@ New project...
function onZoomend(){
map.setView(venue, map.getZoom());
};
- {% endif %}
+ {% endif %}
+ commentsInit("{{ request.base_url }}");
});
diff --git a/hacknight/templates/project_owner_email.md b/hacknight/templates/project_owner_email.md
index 0587e14..4f7c7ff 100644
--- a/hacknight/templates/project_owner_email.md
+++ b/hacknight/templates/project_owner_email.md
@@ -1,4 +1,4 @@
-**{{ g.user.username }}** left a comment on your project **{{ project.title }}**
+**{{ g.user.username }}** left a comment {% if wall %} on the event wall {% else %} on your project {% endif %} **{{ project.title }}**
{{ comment.message }}
diff --git a/hacknight/templates/project_team_email.md b/hacknight/templates/project_team_email.md
index d9fce8a..0e50a37 100644
--- a/hacknight/templates/project_team_email.md
+++ b/hacknight/templates/project_team_email.md
@@ -1,4 +1,4 @@
-**{{ g.user.username }}** left a comment in the project **{{ project.title }}**
+**{{ g.user.username }}** left a comment {% if wall %} on the event wall {% else %} in the project {% endif %} **{{ project.title }}**
{{ comment.message }}
diff --git a/hacknight/views/event.py b/hacknight/views/event.py
index 7b242ee..b851b40 100644
--- a/hacknight/views/event.py
+++ b/hacknight/views/event.py
@@ -1,18 +1,24 @@
# -*- coding: utf-8 -*-
+from datetime import datetime
from sqlalchemy.orm import joinedload
from sqlalchemy import func
from html2text import html2text
from flask.ext.mail import Message
-from flask import render_template, abort, flash, url_for, g, request, Markup
-from coaster.views import load_model, load_models
+from flask import render_template, abort, flash, url_for, g, request, Markup, redirect
+from coaster.views import load_model, load_models, jsonp
from baseframe.forms import render_redirect, render_form, render_delete_sqla
from hacknight import app, mail
-from hacknight.models import db, Profile, Event, User, Participant, PARTICIPANT_STATUS, EventRedirect
+from hacknight.models import db, Profile, Event, User, Participant, PARTICIPANT_STATUS, EventRedirect, Comment
from hacknight.forms.event import EventForm, ConfirmWithdrawForm, SendEmailForm, EmailEventParticipantsForm
from hacknight.forms.participant import ParticipantForm
+from hacknight.forms.comment import CommentForm, DeleteCommentForm
from hacknight.views.login import lastuser
from hacknight.views.workflow import ParticipantWorkflow
+from markdown import Markdown
+import bleach
+
+markdown = Markdown(safe_mode="escape").convert
#map participant status event template
@@ -33,7 +39,7 @@ def send_email(sender, to, subject, body, html=None):
mail.send(msg)
-@app.route('//', methods=["GET"])
+@app.route('//', methods=["GET", "POST"])
@load_models(
(Profile, {'name': 'profile'}, 'profile'),
(Event, {'name': 'event', 'profile': 'profile'}, 'event'))
@@ -53,6 +59,51 @@ def event_view(profile, event):
applied = True
break
current_participant = Participant.get(user=g.user, event=event) if g.user else None
+ comments = sorted(Comment.query.filter_by(commentspace=event.comments, reply_to=None).order_by('created_at').all(),
+ key=lambda c: c.votes.count, reverse=True)
+ commentform = CommentForm()
+ delcommentform = DeleteCommentForm()
+ commentspace = event.comments
+ if request.method == 'POST':
+ if request.form.get('form.id') == 'newcomment' and commentform.validate():
+ if commentform.edit_id.data:
+ comment = commentspace.get_comment(int(commentform.edit_id.data))
+ if comment:
+ if comment.user == g.user:
+ comment.message = commentform.message.data
+ comment.message_html = markdown(comment.message)
+ comment.edited_at = datetime.utcnow()
+ flash("Your comment has been edited", "info")
+ else:
+ flash("You can only edit your own comments", "info")
+ else:
+ flash("No such comment", "error")
+ else:
+ comment = Comment(user=g.user, commentspace=event.comments, message=commentform.message.data)
+ comment.message_html = bleach.linkify(markdown(commentform.message.data))
+ event.comments.count += 1
+ comment.votes.vote(g.user) # Vote for your own comment
+ comment.make_id()
+ db.session.add(comment)
+ flash("Your comment has been posted", "info")
+ db.session.commit()
+ # Redirect despite this being the same page because HTTP 303 is required to not break
+ # the browser Back button
+ return redirect(event.url_for() + "#wall")
+
+ elif request.form.get('form.id') == 'delcomment' and delcommentform.validate():
+ comment = commentspace.get_comment(int(delcommentform.comment_id.data))
+ if comment:
+ if comment.user == g.user:
+ comment.delete()
+ event.comments.count -= 1
+ db.session.commit()
+ flash("Your comment was deleted.", "info")
+ else:
+ flash("You did not post that comment.", "error")
+ else:
+ flash("No such comment.", "error")
+ return redirect(event.url_for() + "#wall")
return render_template('event.html', profile=profile, event=event,
projects=event.projects,
accepted_participants=accepted_participants,
@@ -60,7 +111,60 @@ def event_view(profile, event):
applied=applied,
current_participant=current_participant,
sponsors=event.sponsors,
- workflow=workflow)
+ comments=comments,
+ commentform=commentform,
+ delcommentform=delcommentform)
+
+
+@app.route('///comments//voteup', methods=['GET', 'POST'])
+@lastuser.requires_login
+@load_models(
+ (Profile, {'name': 'profile'}, 'profile'),
+ (Event, {'name': 'event', 'profile': 'profile'}, 'event'),
+ (Comment, {'url_id': 'cid', 'commentspace': 'event.comments'}, 'comment'))
+def wall_voteupcomment(profile, event, comment):
+ comment.votes.vote(g.user, votedown=False)
+ db.session.commit()
+ flash("Your vote has been recorded", "info")
+ return redirect(event.url_for() + "#wall")
+
+
+@app.route('///comments//votedown', methods=['GET', 'POST'])
+@lastuser.requires_login
+@load_models(
+ (Profile, {'name': 'profile'}, 'profile'),
+ (Event, {'name': 'event', 'profile': 'profile'}, 'event'),
+ (Comment, {'url_id': 'cid', 'commentspace': 'event.comments'}, 'comment'))
+def wall_votedowncomment(profile, event, comment):
+ comment.votes.vote(g.user, votedown=True)
+ db.session.commit()
+ flash("Your vote has been recorded", "info")
+ return redirect(event.url_for() + "#wall", code=302)
+
+
+@app.route('///comments//json')
+@load_models(
+ (Profile, {'name': 'profile'}, 'profile'),
+ (Event, {'name': 'event', 'profile': 'profile'}, 'event'),
+ (Comment, {'url_id': 'cid', 'commentspace': 'event.comments'}, 'comment'))
+def wall_jsoncomment(profile, event, comment):
+ # comment = Comment.query.get(cid)
+ if comment:
+ return jsonp(message=comment.message)
+ return jsonp(message='')
+
+
+@app.route('///comments//cancelvote', methods=['GET', 'POST'])
+@lastuser.requires_login
+@load_models(
+ (Profile, {'name': 'profile'}, 'profile'),
+ (Event, {'name': 'event', 'profile': 'profile'}, 'event'),
+ (Comment, {'url_id': 'cid', 'commentspace': 'event.comments'}, 'comment'))
+def wall_votecancelcomment(profile, event, comment):
+ comment.votes.cancelvote(g.user)
+ db.session.commit()
+ flash("Your vote has been withdrawn", "info")
+ return redirect(event.url_for() + "#wall", code=302)
@app.route('//new', methods=['GET', 'POST'])
@@ -163,6 +267,20 @@ def event_update_participant_status(profile, event):
if participant.status == PARTICIPANT_STATUS.WITHDRAWN:
abort(403)
if participant.status != status:
+<<<<<<< HEAD
+ participant.status = status
+ try:
+ text_message = getattr(event, (participants_email_attrs[status] + '_text'))
+ text_message = text_message.replace("*|FULLNAME|*", participant.user.fullname)
+ message = getattr(event, participants_email_attrs[status])
+ message = message.replace("*|FULLNAME|*", participant.user.fullname)
+ if message:
+ send_email(sender=(g.user.fullname, g.user.email), to=participant.email,
+ subject="%s - Hacknight participation status" % event.title , body=text_message, html=message)
+ except KeyError:
+ pass
+ db.session.commit()
+=======
if event.confirmed_participants_count() < event.maximum_participants:
participant.status = status
try:
@@ -178,6 +296,7 @@ def event_update_participant_status(profile, event):
db.session.commit()
else:
flash("Venue capacity is full", "error")
+>>>>>>> master
return "Done"
abort(403)
diff --git a/hacknight/views/project.py b/hacknight/views/project.py
index 87b4a31..714bdc0 100644
--- a/hacknight/views/project.py
+++ b/hacknight/views/project.py
@@ -217,7 +217,7 @@ def project_view(profile, event, project):
db.session.commit()
link = project.url_for("view", _external=True) + "#c" + str(comment.id)
for item in send_email_info:
- email_body = render_template(item.pop('template'), project=project, comment=comment, link=link)
+ email_body = render_template(item.pop('template'), project=project, comment=comment, wall=False, link=link)
if item['to']:
send_email(sender=None, html=markdown(email_body), body=email_body, **item)
# Redirect despite this being the same page because HTTP 303 is required to not break