Skip to content
This repository has been archived by the owner on May 16, 2020. It is now read-only.

Issue 168 #176

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
22 changes: 9 additions & 13 deletions hacknight/forms/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,11 @@
import wtforms.fields.html5
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from baseframe.forms import Form, RichTextField, DateTimeField, ValidName, AvailableName
from hacknight.models import Venue, EVENT_STATUS
from hacknight.models import Venue

__all__ = ['EventForm', 'ConfirmWithdrawForm', 'SendEmailForm']


STATUS_CHOICES = [
(EVENT_STATUS.DRAFT, 'Draft'),
(EVENT_STATUS.PUBLISHED, 'Published'),
(EVENT_STATUS.ACTIVE, 'Active'),
(EVENT_STATUS.COMPLETED, 'Completed'),
(EVENT_STATUS.CANCELLED, 'Cancelled'),
(EVENT_STATUS.CLOSED, 'Closed'),
(EVENT_STATUS.REJECTED, 'Rejected'),
(EVENT_STATUS.WITHDRAWN, 'Withdrawn')
]


class EventForm(Form):
title = wtforms.TextField("Title", description="Name of the Event", validators=[wtforms.validators.Required(), wtforms.validators.NoneOf(values=["new"]), wtforms.validators.length(max=250)])
name = wtforms.TextField("URL name", validators=[wtforms.validators.Optional(), ValidName(),
Expand All @@ -45,12 +33,20 @@ class EventForm(Form):
description=Markup('Venue for this event (<a href="/venue/new">make new</a>)'),
query_factory=lambda: Venue.query, get_label='title',
)
<<<<<<< HEAD
start_datetime = DateTimeField("Start date/time", description="The date and time at which this event begins", validators=[wtf.Required()])
end_datetime = DateTimeField("End date/time", description="The date and time at which this event ends", validators=[wtf.Required()])
ticket_price = wtf.TextField("Ticket price", description="Entry fee, if any, to be paid at the venue", validators=[wtf.validators.length(max=250)])
total_participants = wtf.IntegerField("Venue capacity", description="The number of people this venue can accommodate. Registrations will be closed after that. Use 0 to indicate unlimited capacity", default=50, validators=[wtf.Required()])
website = wtf.html5.URLField("Website", description="Related Website (Optional)", validators=[wtf.Optional(), wtf.validators.length(max=250), wtf.URL()])
=======
start_datetime = DateTimeField("Start date/time", description="The date and time at which this event begins", validators=[wtforms.validators.Required()])
end_datetime = DateTimeField("End date/time", description="The date and time at which this event ends", validators=[wtforms.validators.Required()])
ticket_price = wtforms.TextField("Ticket price", description="Entry fee, if any, to be paid at the venue", validators=[wtforms.validators.length(max=250)])
maximum_participants = wtforms.IntegerField("Venue capacity", description="The number of people this venue can accommodate.", default=50, validators=[wtforms.validators.Required()])
website = wtforms.fields.html5.URLField("Website", description="Related Website (Optional)", validators=[wtforms.validators.Optional(), wtforms.validators.length(max=250), wtforms.validators.URL()])
status = wtforms.SelectField("Event status", description="Current status of this hacknight", coerce=int, choices=STATUS_CHOICES)
>>>>>>> master

def validate_end_datetime(self, field):
if field.data < self.start_datetime.data:
Expand Down
16 changes: 6 additions & 10 deletions hacknight/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,8 @@ class PROFILE_TYPE:

class EVENT_STATUS:
DRAFT = 0
PUBLISHED = 1
ACTIVE = 2
COMPLETED = 3
CANCELLED = 4
CLOSED = 5
REJECTED = 6
WITHDRAWN = 7
PUBLIC = 1
CLOSED = 2


class Profile(ProfileMixin, BaseNameMixin, db.Model):
Expand Down Expand Up @@ -97,8 +92,7 @@ def confirmed_participants_count(self):

def permissions(self, user, inherited=None):
perms = super(Event, self).permissions(user, inherited)
if user is not None and user.userid == self.profile.userid or self.status in [EVENT_STATUS.PUBLISHED,
EVENT_STATUS.ACTIVE, EVENT_STATUS.COMPLETED, EVENT_STATUS.CANCELLED,
if user is not None and user.userid == self.profile.userid or self.status in [EVENT_STATUS.PUBLIC,
EVENT_STATUS.CLOSED]:
perms.add('view')
if user is not None and self.profile.userid in user.user_organizations_owned_ids():
Expand All @@ -107,7 +101,7 @@ def permissions(self, user, inherited=None):
perms.add('send-email')
return perms

def url_for(self, action='view', _external=False):
def url_for(self, action='view', _external=False, **kwargs):
if action == 'view':
return url_for('event_view', profile=self.profile.name, event=self.name, _external=_external)
elif action == 'edit':
Expand All @@ -128,6 +122,8 @@ def url_for(self, action='view', _external=False):
return url_for('event_export', profile=self.profile.name, event=self.name, _external=_external)
elif action == 'send_email':
return url_for('event_send_email', profile=self.profile.name, event=self.name, _external=_external)
elif action == 'event_change':
return url_for('event_change', profile=self.profile.name, event=self.name, method_name=kwargs['method_name'], _external=_external)
elif action == 'email_template':
return url_for('email_template_form', profile=self.profile.name, event=self.name, _external=_external)

Expand Down
31 changes: 31 additions & 0 deletions hacknight/templates/event.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,25 @@ <h1>{{ self.title() }}</h1>

{%- if event.owner_is(g.user) %}
<li class="nav-header">Manage event</li>
<<<<<<< HEAD
<li><a href="{{ event.url_for('open') }}"><span class="icon-wrench">Manage event</span></a></li>
<li><a href="{{ event.url_for('export') }}"><span class="icon-wrench">Export participants list</span></a></li>
<li><a href="{{ event.url_for('edit') }}"><span class="icon-pencil">Edit details...</span></a></li>
<li><a href="{{ event.url_for('delete') }}"><span class="icon-trash">Delete event...</span></a></li>
<li><a href="{{ event.url_for('send_email') }}"><span class="icon-envelope">Email participants...</span></a></li>
<li><a href="{{ event.url_for('email_template') }}"><span class="icon-envelope">Email template...</span></a></li>
{% with transitions = workflow.transitions() %}
{% for key in transitions %}
<li><a href="{{ event.url_for('event_change', method_name=key) }}" data-name="{{ key }}" class="event_change" data-method="POST"><i class="icon-pencil"></i>{{ transitions[key]['description'] }}</a></li>
{% endfor %}
{% endwith %}
{% endif -%}
<li class="nav-header">Share</li>
<li><a target="_blank" href="http://twitter.com/share?url={{ request.url }}&amp;via=HasGeek&amp;text={{ event.title }}" class="socialite twitter-share" data-url="{{ request.url }}" data-text="{{ event.title }}" data-via="HasGeek"><span class="icon-twitter">Twitter</span></a></li>
<li><a target="_blank" href="http://www.facebook.com/sharer.php?u={{ request.url }}&amp;t={{ event.title }}" class="socialite facebook-share" data-href="{{ request.url }}"><span class="icon-facebook">Facebook</span></a></li>
<li><a target="_blank" href="https://plus.google.com/share?url={{ request.url }}" class="socialite googleplus-share" data-href="{{ request.url }}" data-action="share"><span class="icon-google-plus">Google+</span></a></li>
</ul>
=======
<li><a href="{{ event.url_for('open') }}"><i class="icon-wrench"></i><span>Manage event</span></a></li>
<li><a href="{{ event.url_for('export') }}"><i class="icon-wrench"></i><span>Export participant list</span></a></li>
<li><a href="{{ event.url_for('edit') }}"><i class="icon-pencil"></i><span>Edit details</span></a></li>
Expand Down Expand Up @@ -126,6 +145,7 @@ <h4 class="nav-header">Share</h4>
</li>
</ul>
</div>
>>>>>>> master
{% if event.sponsors %}
<hr class="clear-line">
<div class="sidebar-heading nav nav-list">
Expand Down Expand Up @@ -236,6 +256,17 @@ <h2>New project...</h2>
map.setView(venue, map.getZoom());
};
{% endif %}
// Add event handler for hacknight event state change
$(".event_change").on('click', function(event){
event.preventDefault();
$.ajax({
url: this.getAttribute('href'),
type: this.getAttribute('data-method'),
success: function(msg) { //reload same page
window.location.reload(true);
}
});
});
});
</script>

Expand Down
18 changes: 18 additions & 0 deletions hacknight/views/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,24 @@ def event_send_email(profile, event):
submit=u"Send", cancel_url=event.url_for(), ajax=False)


@app.route('/<profile>/<event>/change/<method_name>', methods=['POST'])
@lastuser.requires_login
@load_models(
(Profile, {'name': 'profile'}, 'profile'),
(Event, {'name': 'event', 'profile': 'profile'}, 'event'), permission='edit')
def event_change(profile, event):
if request.is_xhr:
workflow = event.workflow()
method_name = request.view_args['method_name']
try:
getattr(workflow, method_name)()
flash("Event state changed", "success")
except AttributeError:
flash("Unable to change event state", "error")
db.session.commit()
return render_redirect(event.url_for())


@app.route('/<profile>/<event>/email_template', methods=['GET', 'POST'])
@lastuser.requires_login
@load_models(
Expand Down
4 changes: 2 additions & 2 deletions hacknight/views/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
@app.route('/')
def index():
# TODO: Filter events by status
upcoming_events = Event.query.filter(Event.end_datetime > datetime.utcnow(), Event.status != EVENT_STATUS.DRAFT, Event.status != EVENT_STATUS.CANCELLED).order_by(Event.start_datetime.asc()).all()
past_events = Event.query.filter(Event.end_datetime < datetime.utcnow(), Event.status != EVENT_STATUS.DRAFT, Event.status != EVENT_STATUS.CANCELLED).order_by(Event.end_datetime.desc()).all()
upcoming_events = Event.query.filter(Event.end_datetime > datetime.utcnow(), Event.status != EVENT_STATUS.DRAFT).order_by(Event.start_datetime.asc()).all()
past_events = Event.query.filter(Event.end_datetime < datetime.utcnow(), Event.status != EVENT_STATUS.DRAFT).order_by(Event.end_datetime.desc()).all()
return render_template('index.html', upcoming_events=upcoming_events, past_events=past_events)


Expand Down
5 changes: 4 additions & 1 deletion hacknight/views/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ def project_new(profile, event, form=None):
abort(403)
if participant.status != PARTICIPANT_STATUS.CONFIRMED:
abort(403)

workflow = event.workflow()
if not workflow.create_projects():
flash("You cannot create projects since the hacknight is closed.")
return redirect(event.url_for())
form = ProjectForm()
if form.validate_on_submit():
project = Project(event=event, user=g.user)
Expand Down
92 changes: 39 additions & 53 deletions hacknight/views/workflow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# -*- coding: utf-8- *-
# -*- coding: utf-8 -*-

from flask import g
from coaster.docflow import DocumentWorkflow, WorkflowState, WorkflowStateGroup
Expand Down Expand Up @@ -28,11 +28,11 @@ class ParticipantWorkflow(DocumentWorkflow):
path_to_hacknight = WorkflowStateGroup([pending, waiting_list], title=u'Path to hacknight',
description=u'If the participant is in any one of the state he/she can become hacknight member')
reject_member = WorkflowStateGroup([pending, waiting_list],
title=u'Path to remove a member from to hacknight',
description=u'If the participant is in any one of the state he/she can be rejected for hacknight')
title=u'Path to remove a member from to hacknight',
description=u'If the participant is in any one of the state he/she can be rejected for hacknight')
withdrawn_member = WorkflowStateGroup([confirmed, waiting_list, pending],
title=u'Path to withdraw membership',
description=u'If the participant is in any one of the state he/she can withdraw for hacknight')
title=u'Path to withdraw membership',
description=u'If the participant is in any one of the state he/she can withdraw for hacknight')
#copied from geekup, in hacknight only event owner can approve

def permissions(self):
Expand Down Expand Up @@ -90,21 +90,19 @@ class EventWorkflow(DocumentWorkflow):

state_attr = 'status'
draft = WorkflowState(EVENT_STATUS.DRAFT, title=u"Draft")
active = WorkflowState(EVENT_STATUS.ACTIVE, title=u"Active")
closed = WorkflowState(EVENT_STATUS.CLOSED, title=u"Closed")
completed = WorkflowState(EVENT_STATUS.COMPLETED, title=u"Completed")
cancelled = WorkflowState(EVENT_STATUS.CANCELLED, title=u"Cancelled")
rejected = WorkflowState(EVENT_STATUS.REJECTED, title=u"Rejected")
withdrawn = WorkflowState(EVENT_STATUS.WITHDRAWN, title=u"Withdrawn")
published = WorkflowState(EVENT_STATUS.PUBLISHED, title=u"Public")

#: States in which an owner can edit
editable = WorkflowStateGroup([draft, active, closed, completed, cancelled, rejected, withdrawn, published], title=u"Editable")
public = WorkflowStateGroup([active, closed], title=u"Public")
appliable = WorkflowStateGroup([active, published], title="User can apply for an event")
public = WorkflowState(EVENT_STATUS.PUBLIC, title=u"Public")


#: States in which an owner can edit
editable = WorkflowStateGroup([draft, public, closed], title=u"Editable")
public_states = WorkflowStateGroup([public, closed], title=u"Public")
appliable = WorkflowStateGroup([public], title="User can apply for an event")

openit = WorkflowStateGroup([draft], title=u"Open it")
create_projects = WorkflowStateGroup([draft, public], title="States in which projects can be created")
#: States in which a reviewer can view
reviewable = WorkflowStateGroup([draft, active, closed, rejected, completed],
reviewable = WorkflowStateGroup([draft, public],
title=u"Reviewable")

def permissions(self):
Expand All @@ -118,74 +116,58 @@ def permissions(self):
base_permissions.extend(lastuser.permissions())
return base_permissions

@draft.transition(active, 'owner', title=u"Open", category="primary",
description=u"Open the hacknight for registrations.", view="event_open")
@draft.transition(public, 'owner', title=u"Open", category=u"primary",
description=u"Make hacknight public", view=u"event_change")
def openit(self):
"""
Open the hacknight.
"""
if not self.document.status == EVENT_STATUS.PUBLISHED:
self.document.status = EVENT_STATUS.PUBLISHED
if not self.document.status == EVENT_STATUS.PUBLIC:
self.document.status = EVENT_STATUS.PUBLIC

@draft.transition(cancelled, 'owner', title=u"Cancel", category="warning",
description=u"Cancel the hacknight, before opening.", view="event_cancel")
@draft.transition(closed, 'owner', title=u"Cancel", category=u"warning",
description=u"Cancel hacknight", view=u"event_change")
def cancel_draft(self):
"""
Cancel the hacknight
"""
self.document.status = EVENT_STATUS.CANCELLED
self.document.status = EVENT_STATUS.CLOSED

@active.transition(cancelled, 'owner', title=u"Cancel", category="warning",
description=u"Cancel the hacknight, before opening.", view="event_cancel")
def cancel_active(self):
@public.transition(closed, 'owner', title=u"Cancel", category="warning",
description=u"Cancel hacknight", view="event_change")
def cancel_public(self):
"""
Cancel the hacknight
"""
self.document.status = EVENT_STATUS.CANCELLED

@draft.transition(rejected, 'owner', title=u"Rejected", category="danger",
description=u"Reject the hacknight proposed by someone else", view="event_reject")
def reject(self):
"""
Reject the hacknight
"""
pass
self.document.status = EVENT_STATUS.CLOSED

@draft.transition(withdrawn, 'owner', title=u"Withdraw", category="danger",
description=u"Withdraw the hacknight", view="event_withdraw")
def withdraw(self):
"""
Withdraw the hacknight
"""
pass

@active.transition(closed, 'owner', title=u"Close", category="primary",
description=u"Close registrations for the hacknight", view="event_close")
@public.transition(closed, 'owner', title=u"Close", category="primary",
description=u"Close Hacknight", view="event_change")
def close(self):
"""
Close the hacknight
"""
pass
self.document.status = EVENT_STATUS.CLOSED

@closed.transition(completed, 'owner', title=u"Complete", category="success",
description=u"hacknight completed", view="event_completed")
def complete(self):
@closed.transition(public, 'owner', title=u"Complete", category="success",
description=u"Reopen hacknight", view="event_change")
def reopen(self):
"""
Hacknight is now completed.
"""
pass
self.document.status = EVENT_STATUS.PUBLIC

def is_public(self):
"""
Is the hacknight public?
"""
return self.public()
return self.public_states()

def can_view(self):
"""
Can the current user view this?
"""
return self.public() or 'owner' in self.permissions()
return self.public_states() or 'owner' in self.permissions()

def can_edit(self):
"""
Expand All @@ -205,7 +187,11 @@ def can_delete(self):
"""
return 'owner' in self.permissions() and self.editable()

def is_active(self):
return self.create_projects()

def can_apply(self):
return self.appliable()


EventWorkflow.apply_on(Event)