Merge pull request #158 from alberanid/master
maximum number of available tickets
This commit is contained in:
commit
92e8aab55e
3 changed files with 58 additions and 28 deletions
|
@ -25,15 +25,15 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group input-group-lg">
|
<div class="input-group input-group">
|
||||||
<span class="input-group-addon min100">{{'Title' | translate}}</span>
|
<span class="input-group-addon min100">{{'Title' | translate}}</span>
|
||||||
<input type="text" class="form-control" placeholder="{{'Title' | translate}}" ng-model="event.title" ng-required="true">
|
<input type="text" class="form-control" placeholder="{{'Title' | translate}}" ng-model="event.title" ng-required="true">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group input-group-lg top5">
|
<div class="input-group input-group top5">
|
||||||
<span class="input-group-addon min100">{{'Tagline' | translate}}</span>
|
<span class="input-group-addon min100">{{'Tagline' | translate}}</span>
|
||||||
<input type="text" class="form-control" placeholder="{{'Tagline' | translate}}" ng-model="event.tagline">
|
<input type="text" class="form-control" placeholder="{{'Tagline' | translate}}" ng-model="event.tagline">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group input-group-lg top5">
|
<div class="input-group input-group top5">
|
||||||
<span class="input-group-addon min100">{{'Short summary' | translate}}</span>
|
<span class="input-group-addon min100">{{'Short summary' | translate}}</span>
|
||||||
<input type="text" class="form-control" placeholder="{{'Short summary' | translate}}" ng-model="event.summary">
|
<input type="text" class="form-control" placeholder="{{'Short summary' | translate}}" ng-model="event.summary">
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,25 +74,35 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group input-group-lg top5">
|
<div class="input-group input-group top5">
|
||||||
<span class="input-group-addon min100">{{'Where' | translate}}</span>
|
<span class="input-group-addon min100">{{'Where' | translate}}</span>
|
||||||
<input type="text" class="form-control" placeholder="{{'Where' | translate}}" ng-model="event.where">
|
<input type="text" class="form-control" placeholder="{{'Where' | translate}}" ng-model="event.where">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group input-group-lg top5">
|
<div class="input-group input-group top5">
|
||||||
<span class="input-group-addon min100">{{'Group ID' | translate}}</span>
|
<span class="input-group-addon min100">{{'Group ID' | translate}}</span>
|
||||||
<input type="text" class="form-control" placeholder="{{'Used to share persons amongst multiple events. Must be hard to guess (if empty, will be autogenerated)' | translate}}" ng-model="event.group_id">
|
<input type="text" class="form-control" placeholder="{{'Used to share persons amongst multiple events. Must be hard to guess (if empty, will be autogenerated)' | translate}}" ng-model="event.group_id">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="panel panel-default table-striped top5">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h1>{{'Ticket limits'}}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="input-group input-group top5">
|
||||||
|
<span class="input-group-addon min100">{{'Number of tickets' | translate}}</span>
|
||||||
|
<input type="number" min="0" class="form-control" placeholder="{{'Number of tickets (0 or empty means unlimited)' | translate}}" ng-model="event.number_of_tickets">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="submit" class="outside-screen" />
|
||||||
|
<div ng-if="!eventFormDisabled" ng-class="{clearfix: true, alert: true, 'alert-success': !eventForm.$dirty, 'alert-danger': eventForm.$dirty}">
|
||||||
|
<button type="button" class="btn btn-default pull-right" ng-click="save($event)" ng-disabled="!eventForm.$dirty">
|
||||||
|
<span class="fa fa-floppy-o vcenter"></span>
|
||||||
|
{{'save' | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
|
||||||
<label></label>
|
|
||||||
<input type="submit" class="outside-screen" />
|
|
||||||
<div ng-if="!eventFormDisabled" ng-class="{clearfix: true, alert: true, 'alert-success': !eventForm.$dirty, 'alert-danger': eventForm.$dirty}">
|
|
||||||
<button type="button" class="btn btn-default pull-right" ng-click="save($event)" ng-disabled="!eventForm.$dirty">
|
|
||||||
<span class="fa fa-floppy-o vcenter"></span>
|
|
||||||
{{'save' | translate}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -32,7 +32,8 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th><strong>{{'Event' | translate}}</strong></th>
|
<th><strong>{{'Event' | translate}}</strong></th>
|
||||||
<th ng-if="hasPermission('event|update')" class="hcenter"><strong>{{'Attendees / Registered' | translate}}</strong></th>
|
<th ng-if="hasPermission('event|update')" class="hcenter"><strong>{{'Attendees / Registered' | translate}}</strong></th>
|
||||||
<th class="hcenter"><strong>{{'Actions' | translate}}</strong></th>
|
<th class="hcenter"><strong>{{'Tickets' | translate}}</strong></th>
|
||||||
|
<th ng-if="hasPermission('event|update') || hasPermission('event|delete')" class="hcenter"><strong>{{'Actions' | translate}}</strong></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -53,11 +54,14 @@
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td ng-if="hasPermission('event:tickets-all|read')" class="hcenter">
|
<td ng-if="hasPermission('event:tickets-all|read')" class="hcenter">
|
||||||
<p><span ng-init="attendeesNr = ((event.tickets || []) | attendeesFilter).length">{{attendeesNr}}</span> / {{((event.tickets || []) | registeredFilter).length}} ({{((attendeesNr / ((event.tickets || []) | registeredFilter).length * 100) || 0).toFixed()}}%)</p>
|
<p><span ng-init="attendeesNr = ((event.tickets || []) | attendeesFilter).length">{{attendeesNr}}</span> / {{event.tickets_sold || 0}} ({{((attendeesNr / (event.tickets_sold || 0) * 100) || 0).toFixed()}}%)</p>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div ng-if="hasPermission('event:tickets-all|create')" class="top5 hcenter"><button ng-click="$state.go('event.ticket.new', {id: event._id})" class="min150 btn btn-success" type="button" title="{{'Join this event' | translate}}"><span class="fa fa-user-plus vcenter"></span> {{'Join this event' | translate}}</button></div>
|
<div ng-if="hasPermission('event:tickets-all|create')" class="top5 hcenter"><button ng-click="$state.go('event.ticket.new', {id: event._id})" class="min150 btn btn-success" type="button" title="{{'Join this event' | translate}}"><span class="fa fa-user-plus vcenter"></span> {{'Join this event' | translate}}</button></div>
|
||||||
<div ng-if="hasPermission('ticket|update')" class="top5 hcenter"><button ng-click="$state.go('event.tickets', {id: event._id})" class="min150 btn btn-primary" type="button" title="{{'Manage tickets' | translate}}"><span class="fa fa-ticket"></span> {{'Manage tickets' | translate}}</button></div>
|
<div ng-if="hasPermission('ticket|update')" class="top5 hcenter"><button ng-click="$state.go('event.tickets', {id: event._id})" class="min150 btn btn-primary" type="button" title="{{'Manage tickets' | translate}}"><span class="fa fa-ticket"></span> {{'Manage tickets' | translate}}</button></div>
|
||||||
|
<div class="top5 hcenter" ng-if="event.number_of_tickets">{{event.number_of_tickets}} {{'tickets' | translate}}, {{event.number_of_tickets - (event.tickets_sold || 0)}} {{'still available' | translate}}</div>
|
||||||
|
</td>
|
||||||
|
<td ng-if="hasPermission('event|update') || hasPermission('event|delete')">
|
||||||
<div ng-if="hasPermission('event|update')" class="top5 hcenter"><button ng-click="$state.go('event.edit', {id: event._id})" type="button" class="min150 btn btn-warning" title="{{'Edit event' | translate}}"><span class="fa fa-cog"></span> {{'Edit event' | translate}}</button></div>
|
<div ng-if="hasPermission('event|update')" class="top5 hcenter"><button ng-click="$state.go('event.edit', {id: event._id})" type="button" class="min150 btn btn-warning" title="{{'Edit event' | translate}}"><span class="fa fa-cog"></span> {{'Edit event' | translate}}</button></div>
|
||||||
<div ng-if="hasPermission('event|delete')" class="top5 hcenter bottom5"><button ng-click="deleteEvent(event._id)" type="button" class="min150 btn btn-danger" title="{{'Delete event' | translate}}"><span class="fa fa-trash"></span> {{'Delete event' | translate}}</button></div>
|
<div ng-if="hasPermission('event|delete')" class="top5 hcenter bottom5"><button ng-click="deleteEvent(event._id)" type="button" class="min150 btn btn-danger" title="{{'Delete event' | translate}}"><span class="fa fa-trash"></span> {{'Delete event' | translate}}</button></div>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -522,8 +522,9 @@ class CollectionHandler(BaseHandler):
|
||||||
:type env: dict
|
:type env: dict
|
||||||
"""
|
"""
|
||||||
self.ioloop = tornado.ioloop.IOLoop.instance()
|
self.ioloop = tornado.ioloop.IOLoop.instance()
|
||||||
|
processed_env = self._dict2env(env)
|
||||||
p = process.Subprocess(cmd, close_fds=True, stdin=process.Subprocess.STREAM,
|
p = process.Subprocess(cmd, close_fds=True, stdin=process.Subprocess.STREAM,
|
||||||
stdout=process.Subprocess.STREAM, stderr=process.Subprocess.STREAM, env=env)
|
stdout=process.Subprocess.STREAM, stderr=process.Subprocess.STREAM, env=processed_env)
|
||||||
p.set_exit_callback(lambda returncode: self.on_exit(returncode, cmd, p))
|
p.set_exit_callback(lambda returncode: self.on_exit(returncode, cmd, p))
|
||||||
self.timeout = self.ioloop.add_timeout(datetime.timedelta(seconds=PROCESS_TIMEOUT),
|
self.timeout = self.ioloop.add_timeout(datetime.timedelta(seconds=PROCESS_TIMEOUT),
|
||||||
lambda: self.on_timeout(cmd, p))
|
lambda: self.on_timeout(cmd, p))
|
||||||
|
@ -589,15 +590,17 @@ class EventsHandler(CollectionHandler):
|
||||||
collection = 'events'
|
collection = 'events'
|
||||||
|
|
||||||
def filter_get(self, output):
|
def filter_get(self, output):
|
||||||
if not self.has_permission('tickets-all|read'):
|
if 'tickets' in output:
|
||||||
if 'tickets' in output:
|
output['tickets_sold'] = len([t for t in output['tickets'] if not t.get('cancelled')])
|
||||||
|
if not self.has_permission('tickets-all|read'):
|
||||||
output['tickets'] = []
|
output['tickets'] = []
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def filter_get_all(self, output):
|
def filter_get_all(self, output):
|
||||||
if not self.has_permission('tickets-all|read'):
|
for event in output.get('events') or []:
|
||||||
for event in output.get('events') or []:
|
if 'tickets' in event:
|
||||||
if 'tickets' in event:
|
event['tickets_sold'] = len([t for t in event['tickets'] if not t.get('cancelled')])
|
||||||
|
if not self.has_permission('tickets-all|read'):
|
||||||
event['tickets'] = []
|
event['tickets'] = []
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
@ -659,6 +662,12 @@ class EventsHandler(CollectionHandler):
|
||||||
return {'tickets': tickets}
|
return {'tickets': tickets}
|
||||||
|
|
||||||
def handle_post_tickets(self, id_, resource_id, data):
|
def handle_post_tickets(self, id_, resource_id, data):
|
||||||
|
event = self.db.query('events', {'_id': id_})[0]
|
||||||
|
if 'number_of_tickets' in event:
|
||||||
|
tickets = event.get('tickets') or []
|
||||||
|
tickets = [t for t in tickets if not t.get('cancelled')]
|
||||||
|
if len(tickets) >= event['number_of_tickets']:
|
||||||
|
raise InputException('no more tickets available')
|
||||||
uuid, arguments = self.uuid_arguments
|
uuid, arguments = self.uuid_arguments
|
||||||
self._clean_dict(data)
|
self._clean_dict(data)
|
||||||
data['seq'] = self.get_next_seq('event_%s_tickets' % id_)
|
data['seq'] = self.get_next_seq('event_%s_tickets' % id_)
|
||||||
|
@ -673,7 +682,7 @@ class EventsHandler(CollectionHandler):
|
||||||
if doc:
|
if doc:
|
||||||
self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret))
|
self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret))
|
||||||
ticket = self._get_ticket_data(ticket_id, doc.get('tickets') or [])
|
ticket = self._get_ticket_data(ticket_id, doc.get('tickets') or [])
|
||||||
env = self._dict2env(ticket)
|
env = dict(ticket)
|
||||||
env.update({'PERSON_ID': ticket_id, 'TICKED_ID': ticket_id, 'EVENT_ID': id_,
|
env.update({'PERSON_ID': ticket_id, 'TICKED_ID': ticket_id, 'EVENT_ID': id_,
|
||||||
'EVENT_TITLE': doc.get('title', ''), 'WEB_USER': self.current_user,
|
'EVENT_TITLE': doc.get('title', ''), 'WEB_USER': self.current_user,
|
||||||
'WEB_REMOTE_IP': self.request.remote_ip})
|
'WEB_REMOTE_IP': self.request.remote_ip})
|
||||||
|
@ -701,13 +710,20 @@ class EventsHandler(CollectionHandler):
|
||||||
current_event = current_event[0]
|
current_event = current_event[0]
|
||||||
else:
|
else:
|
||||||
current_event = {}
|
current_event = {}
|
||||||
old_ticket_data = self._get_ticket_data(ticket_query,
|
tickets = current_event.get('tickets') or []
|
||||||
current_event.get('tickets') or [])
|
old_ticket_data = self._get_ticket_data(ticket_query, tickets)
|
||||||
|
|
||||||
|
# We updating the "cancelled" status of a ticket; check if we still have a ticket available
|
||||||
|
if 'number_of_tickets' in current_event and old_ticket_data.get('cancelled') and not data.get('cancelled'):
|
||||||
|
active_tickets = [t for t in tickets if not t.get('cancelled')]
|
||||||
|
if len(active_tickets) >= current_event['number_of_tickets']:
|
||||||
|
raise InputException('no more tickets available')
|
||||||
|
|
||||||
merged, doc = self.db.update('events', query,
|
merged, doc = self.db.update('events', query,
|
||||||
data, updateList='tickets', create=False)
|
data, updateList='tickets', create=False)
|
||||||
new_ticket_data = self._get_ticket_data(ticket_query,
|
new_ticket_data = self._get_ticket_data(ticket_query,
|
||||||
doc.get('tickets') or [])
|
doc.get('tickets') or [])
|
||||||
env = self._dict2env(new_ticket_data)
|
env = dict(new_ticket_data)
|
||||||
# always takes the ticket_id from the new ticket
|
# always takes the ticket_id from the new ticket
|
||||||
ticket_id = str(new_ticket_data.get('_id'))
|
ticket_id = str(new_ticket_data.get('_id'))
|
||||||
env.update({'PERSON_ID': ticket_id, 'TICKED_ID': ticket_id, 'EVENT_ID': id_,
|
env.update({'PERSON_ID': ticket_id, 'TICKED_ID': ticket_id, 'EVENT_ID': id_,
|
||||||
|
@ -742,7 +758,7 @@ class EventsHandler(CollectionHandler):
|
||||||
operation='delete',
|
operation='delete',
|
||||||
create=False)
|
create=False)
|
||||||
self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret))
|
self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret))
|
||||||
env = self._dict2env(ticket)
|
env = dict(ticket)
|
||||||
env.update({'PERSON_ID': ticket_id, 'TICKED_ID': ticket_id, 'EVENT_ID': id_,
|
env.update({'PERSON_ID': ticket_id, 'TICKED_ID': ticket_id, 'EVENT_ID': id_,
|
||||||
'EVENT_TITLE': rdoc.get('title', ''), 'WEB_USER': self.current_user,
|
'EVENT_TITLE': rdoc.get('title', ''), 'WEB_USER': self.current_user,
|
||||||
'WEB_REMOTE_IP': self.request.remote_ip})
|
'WEB_REMOTE_IP': self.request.remote_ip})
|
||||||
|
|
Loading…
Reference in a new issue