Skip to content
This repository has been archived by the owner on Sep 5, 2019. It is now read-only.

Commit

Permalink
Adds the ability to export the reservations of a resource
Browse files Browse the repository at this point in the history
Resolves #185
  • Loading branch information
Denis Krienbühl committed May 13, 2016
1 parent ff329a7 commit 7081bbc
Show file tree
Hide file tree
Showing 12 changed files with 335 additions and 43 deletions.
5 changes: 4 additions & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
Changelog
---------

- Adds an occupancy report to reservation resources.
- Adds the ability to export the reservations of a resource.
[href]

- Adds an occupancy report on resource for reservations.
[href]

- Fixes unreserved allocations showing associated tickets.
Expand Down
7 changes: 6 additions & 1 deletion onegov/town/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
PasswordResetForm
)
from onegov.town.forms.reservation import ReservationForm
from onegov.town.forms.resource import ResourceForm, ResourceCleanupForm
from onegov.town.forms.resource import (
ResourceForm,
ResourceCleanupForm,
ResourceExportForm
)
from onegov.town.forms.settings import SettingsForm
from onegov.town.forms.signup import SignupForm
from onegov.town.forms.userprofile import UserProfileForm
Expand All @@ -37,6 +41,7 @@
'ReservationForm',
'ResourceForm',
'ResourceCleanupForm',
'ResourceExportForm',
'RequestPasswordResetForm',
'RoomAllocationForm',
'RoomAllocationEditForm',
Expand Down
27 changes: 24 additions & 3 deletions onegov/town/forms/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from onegov.town import _
from onegov.town.forms.reservation import RESERVED_FIELDS
from onegov.town.utils import annotate_html
from wtforms import StringField, TextAreaField, validators
from wtforms import RadioField, StringField, TextAreaField, validators
from wtforms.fields.html5 import DateField


Expand Down Expand Up @@ -40,8 +40,8 @@ class ResourceForm(Form):
)


class ResourceCleanupForm(Form):
""" Defines the form to remove multiple allocations. """
class DateRangeForm(Form):
""" A form providing a start/end date range. """

start = DateField(
label=_("Start"),
Expand All @@ -63,3 +63,24 @@ def validate(self):
result = False

return result


class ResourceCleanupForm(DateRangeForm):
""" Defines the form to remove multiple allocations. """


class ResourceExportForm(DateRangeForm):
""" Defines the form to export reservations. """

file_format = RadioField(
label=_("Format"),
choices=[
('csv', _("CSV File")),
('xlsx', _("Excel File")),
('json', _("JSON File"))
],
default='csv',
validators=[
validators.InputRequired()
]
)
7 changes: 6 additions & 1 deletion onegov/town/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,12 +783,17 @@ def editbar_links(self):
Link(
text=_("Clean up"),
url=self.request.link(self.model, 'cleanup'),
classes=('cleanup-link', )
classes=('cleanup-link', 'calendar-dependent')
),
Link(
text=_("Occupancy"),
url=self.request.link(self.model, 'belegung'),
classes=('occupancy-link', 'calendar-dependent')
),
Link(
text=_("Export"),
url=self.request.link(self.model, 'export'),
classes=('export-link', 'calendar-dependent')
)
]

Expand Down
44 changes: 43 additions & 1 deletion onegov/town/models/resource.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import sedate

from datetime import datetime
from libres.db.models import Reservation
from onegov.core.orm.mixins import meta_property, content_property
from onegov.core.orm.types import UUID
from onegov.libres.models import Resource
from onegov.form.models import FormSubmission
from onegov.search import ORMSearchable
from onegov.ticket import Ticket
from onegov.town.models.extensions import (
HiddenFromPublicExtension,
ContactExtension,
PersonLinkExtension,
CoordinatesExtension
)
from onegov.search import ORMSearchable
from sqlalchemy.sql.expression import cast
from uuid import uuid4, uuid5


Expand All @@ -27,6 +33,23 @@ def deletable(self):

return True

@property
def calendar_date_range(self):
""" Returns the date range set by the fullcalendar specific params. """

if self.date:
date = datetime(self.date.year, self.date.month, self.date.day)
date = sedate.replace_timezone(date, self.timezone)
else:
date = sedate.to_timezone(sedate.utcnow(), self.timezone)

if self.view == 'month':
return sedate.align_range_to_month(date, date, self.timezone)
elif self.view == 'agendaWeek':
return sedate.align_range_to_week(date, date, self.timezone)
elif self.view == 'agendaDay':
return sedate.align_range_to_day(date, date, self.timezone)

def remove_expired_reservation_sessions(self, expiration_date=None):
session = self.libres_context.get_service('session_provider').session()
queries = self.scheduler.queries
Expand Down Expand Up @@ -67,6 +90,25 @@ def bound_session_id(self, request):

return uuid5(self.id, request.browser_session.libres_session_id.hex)

def reservations_with_tickets_query(self, start, end):
""" Returns a query which joins this resource's reservations between
start and end with the tickets table.
"""
query = self.scheduler.managed_reservations()
query = query.filter(start <= Reservation.start)
query = query.filter(Reservation.end <= end)

query = query.join(
Ticket, Reservation.token == cast(Ticket.handler_id, UUID))

query = query.order_by(Reservation.start)
query = query.order_by(Ticket.subtitle)
query = query.filter(Reservation.status == 'approved')
query = query.filter(Reservation.data != None)

return query


class SearchableResource(ORMSearchable):

Expand Down
2 changes: 1 addition & 1 deletion onegov/town/templates/macros.pt
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
</metal:bottom_links>

<metal:form define-macro="form">
<form tal:attributes="action form.action|'.';data-image-upload-url layout.image_upload_json_url; data-image-list-url layout.image_list_url;data-file-upload-url layout.file_upload_json_url; data-file-list-url layout.file_list_url; data-sitecollection-url layout.sitecollection_url;" method="POST" enctype="multipart/form-data">
<form tal:attributes="action form.action|'.';data-image-upload-url layout.image_upload_json_url; data-image-list-url layout.image_list_url;data-file-upload-url layout.file_upload_json_url; data-file-list-url layout.file_list_url; data-sitecollection-url layout.sitecollection_url;" method="${form.method|'POST'}" enctype="multipart/form-data">
<tal:block repeat="fieldset form.fieldsets">
<fieldset tal:condition="fieldset.is_visible" id="fieldset-${fieldset.label.lower()}">
<legend>${fieldset.label}</legend>
Expand Down
15 changes: 15 additions & 0 deletions onegov/town/templates/resource_export.pt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div metal:use-macro="layout.base" i18n:domain="onegov.town">
<tal:block metal:fill-slot="title">
${title}
</tal:block>
<tal:block metal:fill-slot="content">
<div class="panel" i18n:translate>
Exports the reservations of the given date range.
</div>
<div class="row">
<div class="small-12 medium-8 large-6 columns">
<div metal:use-macro="layout.macros['form']" />
</div>
</div>
</tal:block>
</div>
4 changes: 2 additions & 2 deletions onegov/town/templates/resource_occupancy.pt
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
<div class="small-12 medium-8 columns">
<div class="row occupancy-block" tal:repeat="date occupancy">
<tal:block define="entries occupancy[date]">
<div class="columns small-12 medium-3">
<div class="columns small-12 medium-12 large-3">
<metal:block use-macro="layout.macros['calendar-day']" />
</div>
<div class="columns small-12 medium-9">
<div class="columns small-12 medium-12 large-9">
<ul class="occupancy-entry">
<li tal:repeat="entry entries">
<tal:block tal:define="whole_day entry.start.time() != entry.end.time()">
Expand Down
35 changes: 35 additions & 0 deletions onegov/town/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pytz

from datetime import datetime, date
from freezegun import freeze_time
from io import BytesIO
from onegov.core.request import CoreRequest
from onegov.core.utils import module_path, rchop
Expand All @@ -13,6 +14,7 @@
ImageCollection,
SiteCollection
)
from onegov.town.models.resource import SharedMethods
from unittest.mock import Mock, patch


Expand Down Expand Up @@ -270,3 +272,36 @@ def __init__(self, mod_time):
assert groups[2][1] == images[5:6]
assert groups[3][0] == 'Older'
assert groups[3][1] == images[6:]


def test_calendar_date_range():
resource = SharedMethods()
utc = pytz.timezone('UTC')

resource.date = None
resource.timezone = utc

resource.view = 'month'
with freeze_time(datetime(2016, 5, 14, tzinfo=utc)):
assert resource.calendar_date_range == (
datetime(2016, 5, 1, tzinfo=utc),
datetime(2016, 5, 31, 23, 59, 59, 999999, tzinfo=utc)
)

resource.date = date(2016, 5, 14)
assert resource.calendar_date_range == (
datetime(2016, 5, 1, tzinfo=utc),
datetime(2016, 5, 31, 23, 59, 59, 999999, tzinfo=utc)
)

resource.view = 'agendaWeek'
assert resource.calendar_date_range == (
datetime(2016, 5, 9, tzinfo=utc),
datetime(2016, 5, 15, 23, 59, 59, 999999, tzinfo=utc)
)

resource.view = 'agendaDay'
assert resource.calendar_date_range == (
datetime(2016, 5, 14, tzinfo=utc),
datetime(2016, 5, 14, 23, 59, 59, 999999, tzinfo=utc)
)
50 changes: 50 additions & 0 deletions onegov/town/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1811,6 +1811,56 @@ def test_occupancy_view(town_app):
assert len(occupancy.pyquery('.occupancy-block')) == 1


def test_reservation_export_view(town_app):

# prepate the required data
resources = ResourceCollection(town_app.libres_context)
resource = resources.by_name('sbb-tageskarte')
resource.definition = "Vorname *= ___\nNachname *= ___"

scheduler = resource.get_scheduler(town_app.libres_context)

allocations = scheduler.allocate(
dates=(datetime(2015, 8, 28), datetime(2015, 8, 28)),
whole_day=True
)

client = Client(town_app)
reserve = bound_reserve(client, allocations[0])
transaction.commit()

client.login_admin()

# create a reservation
assert reserve().json == {'success': True}
formular = client.get('/ressource/sbb-tageskarte/formular')
formular.form['email'] = 'info@example.org'
formular.form['vorname'] = 'Charlie'
formular.form['nachname'] = 'Carson'
formular.form.submit().follow().click('Abschliessen')

ticket = client.get('/tickets/ALL/open').click('Annehmen').follow()

# at this point, the reservation won't show up in the export
export = client.get('/ressource/sbb-tageskarte/export')
export.form['start'] = date(2015, 8, 28)
export.form['end'] = date(2015, 8, 28)
export.form['file_format'] = 'json'
assert not export.form.submit().json

# until we confirm the reservation
ticket.click('Alle Reservationen annehmen')
charlie = export.form.submit().json[0]

assert charlie['email'] == 'info@example.org'
assert charlie['title'] == 'info@example.org, Charlie, Carson'
assert charlie['start'] == '2015-08-28T00:00:00+02:00'
assert charlie['end'] == '2015-08-29T00:00:00+02:00'
assert charlie['ticket'].startswith('RSV-')
assert charlie['quota'] == 1
assert charlie['form'] == {'vorname': 'Charlie', 'nachname': 'Carson'}


def test_reserve_session_separation(town_app):
c1 = Client(town_app)
c1.login_admin()
Expand Down
13 changes: 11 additions & 2 deletions onegov/town/theme/styles/town.scss
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ $copy-link-icon: '\f0c5';
$delete-link-icon: '\f014';
$disabled-icon: '\f05e';
$edit-link-icon: '\f040';
$export-link-icon: '\f019';
$file-link-icon: '\f0c6';
$image-link-icon: '\f03e';
$internal-link-icon: '\f0c1';
Expand Down Expand Up @@ -734,6 +735,7 @@ $occupancy-link-icon: '\f022';
.copy-link:before { @include icon($copy-link-icon); }
.delete-link:before { @include icon($delete-link-icon); }
.edit-link:before { @include icon($edit-link-icon); }
.export-link:before { @include icon($export-link-icon); }
.file-url:before { @include icon($file-link-icon); }
.image-url:before { @include icon($image-link-icon); }
.internal-url:before { @include icon($internal-link-icon); }
Expand Down Expand Up @@ -2045,16 +2047,21 @@ button {
</h2>
*/
.calendar-day-box {
padding: .5em 0;
width: 100%;

@media #{$small-up} {
font-weight: bold;
padding: 0;

span {
font-size: 1.3rem;
}
}

@media #{$large-up} {
background-color: $white-smoke;
font-weight: normal;
padding: .5em 0;

span {
display: block;
Expand Down Expand Up @@ -2715,7 +2722,9 @@ ul.search-results {
Occupancy report
*/
.occupancy-block > .columns {
margin-bottom: 1rem;
@media #{$large-only} {
margin-bottom: 1rem;
}
}

.occupancy-entry {
Expand Down
Loading

0 comments on commit 7081bbc

Please sign in to comment.