├── brambling ├── api │ ├── __init__.py │ ├── v1 │ │ ├── __init__.py │ │ ├── endpoints │ │ │ ├── __init__.py │ │ │ ├── dancestyle.py │ │ │ ├── housingcategory.py │ │ │ ├── environmentalfactor.py │ │ │ ├── item.py │ │ │ ├── itemimage.py │ │ │ ├── event.py │ │ │ ├── ordersearch.py │ │ │ ├── itemoption.py │ │ │ └── organization.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_ordersearch.py │ │ │ └── test_attendee.py │ │ └── urls.py │ └── urls.py ├── forms │ └── __init__.py ├── tests │ ├── __init__.py │ ├── unit │ │ ├── __init__.py │ │ ├── test_utils_payment.py │ │ ├── test_utils_international.py │ │ └── test_model_tables.py │ └── functional │ │ ├── __init__.py │ │ ├── test_attendee_model.py │ │ ├── test_processed_stripe_event_model.py │ │ ├── fixtures │ │ └── stripe_webhook_charge_not_found.yaml │ │ └── test_finance_table.py ├── utils │ ├── __init__.py │ ├── timezones.py │ └── international.py ├── views │ └── __init__.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── send_daily_emails.py ├── migrations │ ├── __init__.py │ ├── 0023_remove_person_dance_styles.py │ ├── 0029_remove_person_modified_directly.py │ ├── 0014_remove_attendee_person_confirmed.py │ ├── 0019_auto_20150528_1850.py │ ├── 0006_remove_organization_check_postmark_cutoff.py │ ├── 0033_attendee_notes.py │ ├── 0048_auto_20160204_0314.py │ ├── 0032_auto_20151010_0624.py │ ├── 0004_event_check_postmark_cutoff.py │ ├── 0012_customformfield_help_text.py │ ├── 0010_auto_20150528_1805.py │ ├── 0053_auto_20160425_2258.py │ ├── 0003_auto_20150514_2049.py │ ├── 0005_copy_check_postmark_cutoff.py │ ├── 0025_auto_20151002_2116.py │ ├── 0049_auto_20160213_0401.py │ ├── 0054_processedstripeevent.py │ ├── 0024_auto_20150924_1842.py │ ├── 0036_auto_20151219_0132.py │ ├── 0030_auto_20151009_0631.py │ ├── 0057_auto_20190105_0626.py │ ├── 0022_auto_20150908_1533.py │ ├── 0016_auto_20150528_1808.py │ ├── 0028_auto_20151003_2301.py │ ├── 0018_auto_20150528_1846.py │ ├── 0007_savedreport.py │ ├── 0013_auto_20150605_1920.py │ ├── 0011_auto_20150603_1708.py │ ├── 0020_auto_20150609_0033.py │ ├── 0044_auto_20160112_2020.py │ ├── 0034_auto_20151104_0259.py │ ├── 0031_auto_20151010_0621.py │ ├── 0045_auto_20160104_2208.py │ ├── 0056_auto_20170606_1844.py │ ├── 0058_auto_20190114_0455.py │ ├── 0055_auto_20170601_2144.py │ ├── 0042_auto_20160105_2012.py │ ├── 0050_auto_20160228_0059.py │ ├── 0035_auto_20151202_1727.py │ ├── 0002_auto_20150502_0912.py │ ├── 0015_auto_20150528_1806.py │ ├── 0052_auto_20160318_0712.py │ ├── 0017_auto_20150528_1846.py │ ├── 0027_saved_attendee_data.py │ └── 0009_set_stripe_metadata.py ├── payment │ ├── __init__.py │ ├── stripe │ │ ├── __init__.py │ │ ├── core.py │ │ └── auth.py │ ├── tests │ │ └── __init__.py │ └── core.py ├── templatetags │ ├── __init__.py │ ├── brambling_forms.py │ ├── brambling_plaintext.py │ └── brambling_markdown.py ├── __init__.py ├── admin │ └── __init__.py ├── templates │ ├── brambling │ │ ├── mail │ │ │ ├── email_confirm │ │ │ │ ├── subject.txt │ │ │ │ └── body_plaintext.txt │ │ │ ├── daily_digest │ │ │ │ ├── subject.txt │ │ │ │ └── body_plaintext.txt │ │ │ ├── invite_event │ │ │ │ ├── subject.txt │ │ │ │ └── body_plaintext.txt │ │ │ ├── order_receipt │ │ │ │ ├── subject.txt │ │ │ │ └── body_plaintext.txt │ │ │ ├── invite_org_edit │ │ │ │ ├── subject.txt │ │ │ │ └── body_plaintext.txt │ │ │ ├── invite_org_view │ │ │ │ ├── subject.txt │ │ │ │ └── body_plaintext.txt │ │ │ ├── invite_event_edit │ │ │ │ ├── subject.txt │ │ │ │ └── body_plaintext.txt │ │ │ ├── invite_event_view │ │ │ │ ├── subject.txt │ │ │ │ └── body_plaintext.txt │ │ │ ├── invite_org_owner │ │ │ │ ├── subject.txt │ │ │ │ └── body_plaintext.txt │ │ │ ├── invite_transfer │ │ │ │ ├── subject.txt │ │ │ │ └── body_plaintext.txt │ │ │ └── order_alert │ │ │ │ ├── subject.txt │ │ │ │ └── body_plaintext.txt │ │ ├── event │ │ │ ├── _where.html │ │ │ ├── order │ │ │ │ ├── _next_step_button.html │ │ │ │ ├── _completed_transfers_summary.html │ │ │ │ ├── _steps_ractive.html │ │ │ │ ├── email.html │ │ │ │ ├── _pending_transfers_summary.html │ │ │ │ ├── _steps.html │ │ │ │ ├── _check_payment_info.html │ │ │ │ ├── _attendee_summary.html │ │ │ │ ├── transfer.html │ │ │ │ └── _use_discount_js.html │ │ │ ├── organizer │ │ │ │ ├── _nav_item.html │ │ │ │ ├── orders.html │ │ │ │ ├── attendees.html │ │ │ │ ├── __narrow.html │ │ │ │ ├── item_form.html │ │ │ │ ├── discount_form.html │ │ │ │ ├── danger_zone.html │ │ │ │ ├── _nav.html │ │ │ │ ├── refund_form.html │ │ │ │ ├── discount_list.html │ │ │ │ ├── item_list.html │ │ │ │ ├── _quickfind_ractive.html │ │ │ │ └── __base.html │ │ │ ├── _empty_shop_alert.html │ │ │ └── _when.html │ │ ├── _disable_forms_js.html │ │ ├── forms │ │ │ ├── rows │ │ │ │ ├── no_label.html │ │ │ │ └── no_label_no_errors.html │ │ │ ├── signup_row.html │ │ │ ├── markdown │ │ │ │ └── field.html │ │ │ ├── _top_error_list.html │ │ │ ├── hosting.html │ │ │ ├── itemimage.html │ │ │ ├── signup.html │ │ │ ├── home.html │ │ │ └── customformfield.html │ │ ├── invites │ │ │ ├── event.html │ │ │ ├── org_edit.html │ │ │ ├── org_view.html │ │ │ ├── event_edit.html │ │ │ ├── event_view.html │ │ │ ├── org_owner.html │ │ │ └── transfer.html │ │ ├── organization │ │ │ ├── _where.html │ │ │ ├── __admin.html │ │ │ └── _nav.html │ │ ├── _stripe_note.html │ │ ├── layouts │ │ │ ├── 12_sm.html │ │ │ ├── 12_xs.html │ │ │ ├── 12.html │ │ │ ├── _messages.html │ │ │ ├── 11_1.html │ │ │ ├── 3_9.html │ │ │ ├── 6_6.html │ │ │ ├── 8_4.html │ │ │ └── 9_3.html │ │ ├── _footer.html │ │ ├── __base_xl.html │ │ ├── __base_fluid.html │ │ ├── _stripe_form.html │ │ ├── user │ │ │ ├── home.html │ │ │ ├── __base_organize.html │ │ │ ├── __base_orders.html │ │ │ ├── notifications.html │ │ │ ├── organizations.html │ │ │ ├── admin_events.html │ │ │ └── __base.html │ │ ├── _stripe_js.html │ │ ├── global.html │ │ ├── _organization_list-group-item.html │ │ ├── creditcard_add.html │ │ └── _event_list-group-item.html │ ├── registration │ │ ├── password_reset_complete.html │ │ ├── logged_out.html │ │ ├── password_reset_form.html │ │ ├── sign_up.html │ │ ├── password_reset_done.html │ │ ├── login.html │ │ └── password_reset_confirm.html │ ├── 418.html │ ├── 404.html │ └── 500.html ├── static │ └── brambling │ │ ├── sass │ │ ├── modules │ │ │ ├── _shortened.sass │ │ │ ├── _list-item.sass │ │ │ ├── _modals.sass │ │ │ ├── _colors.sass │ │ │ ├── _scrim.sass │ │ │ ├── _dancerfly.sass │ │ │ ├── _footer.sass │ │ │ ├── _images.sass │ │ │ ├── _popovers.sass │ │ │ ├── _list-group.sass │ │ │ ├── _base.sass │ │ │ ├── _carousel.sass │ │ │ ├── _menus.sass │ │ │ ├── _headings.sass │ │ │ ├── _event-listing.sass │ │ │ ├── _panels.sass │ │ │ ├── _mixins.sass │ │ │ ├── _forms.sass │ │ │ ├── _marketing.sass │ │ │ └── _navbar.sass │ │ ├── styles.sass │ │ └── _variables.sass │ │ ├── favicon.ico │ │ ├── images │ │ ├── banner.jpg │ │ ├── BluesWings.jpeg │ │ ├── ContraWings.jpeg │ │ ├── all-dancers.gif │ │ ├── WatchersWings.jpeg │ │ ├── little-weaver.png │ │ ├── watcher-cropped.jpg │ │ ├── SwingCoupleWings.jpeg │ │ └── WatcherTwoWings.jpeg │ │ ├── fonts │ │ ├── stryde │ │ │ ├── 2B3B83_0_0.eot │ │ │ ├── 2B3B83_0_0.ttf │ │ │ └── 2B3B83_0_0.woff │ │ ├── stryde-bold │ │ │ ├── 2BBB6D_0_0.eot │ │ │ ├── 2BBB6D_0_0.ttf │ │ │ └── 2BBB6D_0_0.woff │ │ ├── stryde.css │ │ └── stryde-bold.css │ │ ├── lib │ │ ├── chosen │ │ │ ├── chosen-sprite.png │ │ │ ├── chosen-sprite@2x.png │ │ │ └── docsupport │ │ │ │ ├── chosen.png │ │ │ │ └── oss-credit.png │ │ └── ractive-transitions-slide.js │ │ ├── javascript │ │ └── namespaces.js │ │ ├── brambling.slug.js │ │ └── brambling.utils.js ├── storage.py ├── config.rb ├── context_processors.py └── auth_backends.py ├── dancerfly_project ├── __init__.py ├── wsgi.py └── urls.py ├── Procfile ├── .flake8 ├── bin └── docker-entrypoint.sh ├── resources └── favicon.iconsproj ├── .gitignore ├── Dockerfile ├── manage.py ├── .editorconfig ├── docker-compose.yaml ├── .travis.yml ├── LICENSE └── Pipfile /brambling/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/api/v1/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/forms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/payment/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dancerfly_project/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/api/v1/endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/api/v1/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/payment/stripe/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/payment/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /brambling/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = (0, '1a') 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn dancerfly_project.wsgi --log-file - 2 | -------------------------------------------------------------------------------- /brambling/admin/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from brambling.admin.admin import * 3 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude: .svn,CVS,.bzr,.hg,.git,__pycache,node_modules 3 | ignore: E501,F405 4 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/email_confirm/subject.txt: -------------------------------------------------------------------------------- 1 | Confirm your {{ site.name }} address! 2 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_shortened.sass: -------------------------------------------------------------------------------- 1 | .shortened > :last-child 2 | margin-bottom: 0 3 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/daily_digest/subject.txt: -------------------------------------------------------------------------------- 1 | [Dancerfly] New purchases for your events! 2 | -------------------------------------------------------------------------------- /bin/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | pipenv run ./manage.py migrate 5 | pipenv run $@ 6 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_event/subject.txt: -------------------------------------------------------------------------------- 1 | You've been invited to attend {{ content.name|safe }}! 2 | -------------------------------------------------------------------------------- /resources/favicon.iconsproj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/resources/favicon.iconsproj -------------------------------------------------------------------------------- /brambling/templates/brambling/event/_where.html: -------------------------------------------------------------------------------- 1 | {{ event.city }}, {{ event.state_or_province }}, {{ event.country }} 2 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/order_receipt/subject.txt: -------------------------------------------------------------------------------- 1 | [{{ event.name|safe }}] Receipt for order {{ order.code }} 2 | -------------------------------------------------------------------------------- /brambling/static/brambling/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/favicon.ico -------------------------------------------------------------------------------- /brambling/static/brambling/images/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/images/banner.jpg -------------------------------------------------------------------------------- /brambling/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url, include 2 | 3 | urlpatterns = [ 4 | url(r'^v1/', include('brambling.api.v1.urls')), 5 | ] 6 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_org_edit/subject.txt: -------------------------------------------------------------------------------- 1 | {{ invite.user.get_full_name|safe }} has invited you to help manage {{ content.name|safe }} 2 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_org_view/subject.txt: -------------------------------------------------------------------------------- 1 | {{ invite.user.get_full_name|safe }} has invited you to help manage {{ content.name|safe }} 2 | -------------------------------------------------------------------------------- /brambling/static/brambling/images/BluesWings.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/images/BluesWings.jpeg -------------------------------------------------------------------------------- /brambling/static/brambling/images/ContraWings.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/images/ContraWings.jpeg -------------------------------------------------------------------------------- /brambling/static/brambling/images/all-dancers.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/images/all-dancers.gif -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_event_edit/subject.txt: -------------------------------------------------------------------------------- 1 | {{ invite.user.get_full_name|safe }} has invited you to collaborate on {{ content.name|safe }} 2 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_event_view/subject.txt: -------------------------------------------------------------------------------- 1 | {{ invite.user.get_full_name|safe }} has invited you to collaborate on {{ content.name|safe }} 2 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_org_owner/subject.txt: -------------------------------------------------------------------------------- 1 | {{ invite.user.get_full_name|safe }} has invited you to help manage {{ content.name|safe }} 2 | -------------------------------------------------------------------------------- /brambling/static/brambling/images/WatchersWings.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/images/WatchersWings.jpeg -------------------------------------------------------------------------------- /brambling/static/brambling/images/little-weaver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/images/little-weaver.png -------------------------------------------------------------------------------- /brambling/static/brambling/images/watcher-cropped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/images/watcher-cropped.jpg -------------------------------------------------------------------------------- /brambling/static/brambling/fonts/stryde/2B3B83_0_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/fonts/stryde/2B3B83_0_0.eot -------------------------------------------------------------------------------- /brambling/static/brambling/fonts/stryde/2B3B83_0_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/fonts/stryde/2B3B83_0_0.ttf -------------------------------------------------------------------------------- /brambling/static/brambling/fonts/stryde/2B3B83_0_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/fonts/stryde/2B3B83_0_0.woff -------------------------------------------------------------------------------- /brambling/static/brambling/images/SwingCoupleWings.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/images/SwingCoupleWings.jpeg -------------------------------------------------------------------------------- /brambling/static/brambling/images/WatcherTwoWings.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/images/WatcherTwoWings.jpeg -------------------------------------------------------------------------------- /brambling/static/brambling/lib/chosen/chosen-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/lib/chosen/chosen-sprite.png -------------------------------------------------------------------------------- /brambling/static/brambling/lib/chosen/chosen-sprite@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/lib/chosen/chosen-sprite@2x.png -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_list-item.sass: -------------------------------------------------------------------------------- 1 | .list-item 2 | display: flex 3 | flex-direction: row 4 | justify-content: space-between 5 | align-items: center 6 | -------------------------------------------------------------------------------- /brambling/static/brambling/fonts/stryde-bold/2BBB6D_0_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/fonts/stryde-bold/2BBB6D_0_0.eot -------------------------------------------------------------------------------- /brambling/static/brambling/fonts/stryde-bold/2BBB6D_0_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/fonts/stryde-bold/2BBB6D_0_0.ttf -------------------------------------------------------------------------------- /brambling/static/brambling/fonts/stryde-bold/2BBB6D_0_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/fonts/stryde-bold/2BBB6D_0_0.woff -------------------------------------------------------------------------------- /brambling/static/brambling/lib/chosen/docsupport/chosen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/lib/chosen/docsupport/chosen.png -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_modals.sass: -------------------------------------------------------------------------------- 1 | .modal-title 2 | margin-right: $line-height-computed 3 | 4 | #countdownCompleteModal 5 | a.btn 6 | white-space: normal 7 | -------------------------------------------------------------------------------- /brambling/static/brambling/lib/chosen/docsupport/oss-credit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dancerfly/django-brambling/HEAD/brambling/static/brambling/lib/chosen/docsupport/oss-credit.png -------------------------------------------------------------------------------- /brambling/templates/brambling/_disable_forms_js.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /brambling/templates/brambling/forms/rows/no_label.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/forms/rows/bootstrap.html" %} 2 | 3 | {# Blank the label block: #} 4 | {% block label %}{% endblock %} 5 | -------------------------------------------------------------------------------- /brambling/templates/brambling/invites/event.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/invites/__base.html' %} 2 | 3 | {% block invite_title %} 4 | You've been invited to an event! 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_colors.sass: -------------------------------------------------------------------------------- 1 | // TODO: Make this automatic like we do in other projects: 2 | 3 | .color-black 4 | color: #000 5 | 6 | a.color-black:hover 7 | color: #222 8 | -------------------------------------------------------------------------------- /brambling/templates/brambling/invites/org_edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/invites/__base.html' %} 2 | 3 | {% block invite_title %} 4 | You've been invited to join an organization! 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /brambling/templates/brambling/invites/org_view.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/invites/__base.html' %} 2 | 3 | {% block invite_title %} 4 | You've been invited to join an organization! 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_transfer/subject.txt: -------------------------------------------------------------------------------- 1 | {% if invite.user %}{{ invite.user.get_full_name|safe }}{% else %}{{ content.order.email }}{% endif %} wants to transfer an item to you 2 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/order_alert/subject.txt: -------------------------------------------------------------------------------- 1 | [{{ event.name|safe }}] New purchase by {% if order.person %}{{ order.person.get_full_name|safe }}{% else %}{{ order.email }}{% endif %} 2 | -------------------------------------------------------------------------------- /brambling/static/brambling/javascript/namespaces.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | "use strict"; 3 | // Create a space to register our ractive components in: 4 | var components = window.components = {}; 5 | }()); 6 | -------------------------------------------------------------------------------- /brambling/templates/brambling/invites/event_edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/invites/__base.html' %} 2 | 3 | {% block invite_title %} 4 | You've been invited to collaborate on an event! 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /brambling/templates/brambling/invites/event_view.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/invites/__base.html' %} 2 | 3 | {% block invite_title %} 4 | You've been invited to collaborate on an event! 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /brambling/templates/brambling/invites/org_owner.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/invites/__base.html' %} 2 | 3 | {% block invite_title %} 4 | You've been invited to join an organization! 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_scrim.sass: -------------------------------------------------------------------------------- 1 | .scrim 2 | +striped-bg(rgba(235, 235, 235, .5), 10%, rgba($brand-warning, .25)) 3 | position: absolute 4 | top: 0 5 | left: 0 6 | bottom: 0 7 | right: 0 8 | z-index: 3 9 | -------------------------------------------------------------------------------- /brambling/templates/brambling/organization/_where.html: -------------------------------------------------------------------------------- 1 | {% if organization.city %}{{ organization.city }},{% endif %} 2 | {% if organization.state_or_province %}{{ organization.state_or_province }},{% endif %} 3 | {{ organization.country }} 4 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_dancerfly.sass: -------------------------------------------------------------------------------- 1 | .dancerfly-brand 2 | font-family: $headings-font-family 3 | font-weight: $headings-font-weight-normal 4 | +st-ryde-match-x-height 5 | 6 | #dancerflyBrandName 7 | vertical-align: top 8 | -------------------------------------------------------------------------------- /brambling/storage.py: -------------------------------------------------------------------------------- 1 | from storages.backends.s3boto3 import S3Boto3Storage 2 | 3 | 4 | class StaticS3Storage(S3Boto3Storage): 5 | location = 'static' 6 | 7 | 8 | class MediaS3Storage(S3Boto3Storage): 9 | location = 'media' 10 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_footer.sass: -------------------------------------------------------------------------------- 1 | footer 2 | margin-top: 2*$line-height-computed 3 | padding: 0 $grid-gutter-width/2 4 | text-align: center 5 | color: lighten($gray-light,10%) 6 | a 7 | color: lighten(desaturate($link-color,40%),10%) 8 | 9 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/order/_next_step_button.html: -------------------------------------------------------------------------------- 1 | {% firstof button_text "Next step" %} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .env 3 | /*.egg-info/ 4 | /media/ 5 | /static/ 6 | /db.sqlite3 7 | .sass-cache 8 | /.tox/ 9 | /pillar/ 10 | /dancerfly_project/local_settings.py 11 | 12 | # Deprecated after settings.py restructure 13 | /dancerfly_project/media/ 14 | /dancerfly_project/static/ 15 | -------------------------------------------------------------------------------- /brambling/utils/timezones.py: -------------------------------------------------------------------------------- 1 | import pytz 2 | from django.utils import timezone 3 | 4 | 5 | def format_as_localtime(timestamp, fmt, local_timezone): 6 | tz = pytz.timezone(local_timezone) 7 | localized = timezone.localtime(timestamp, timezone=tz) 8 | return localized.strftime(fmt) 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2 2 | ENV PYTHONUNBUFFERED 1 3 | RUN pip install pipenv 4 | RUN mkdir /django 5 | WORKDIR /django 6 | COPY Pipfile* /django/ 7 | RUN pipenv install 8 | COPY . /code/ 9 | ENTRYPOINT ["bin/docker-entrypoint.sh"] 10 | CMD ["./manage.py", "runserver", "0.0.0.0:8000"] 11 | -------------------------------------------------------------------------------- /brambling/templates/brambling/forms/rows/no_label_no_errors.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/forms/rows/bootstrap.html" %} 2 | 3 | {# Blank the label block: #} 4 | {% block label %}{% endblock %} 5 | 6 | {# For the love of god, please render errors *somewhere* though #} 7 | {% block errors %}{% endblock %} 8 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_images.sass: -------------------------------------------------------------------------------- 1 | .item-img 2 | margin-left: $panel-body-padding 3 | &:hover 4 | opacity: .7 5 | cursor: pointer 6 | 7 | .image-framed 8 | border: 4px solid #FFF 9 | background: #FFF 10 | border-radius: 2px 11 | +box-shadow(0 0 0 1px rgba(0,0,0,.35)) 12 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_popovers.sass: -------------------------------------------------------------------------------- 1 | .popover-hugger 2 | // https://github.com/twbs/bootstrap-sass/blob/master/assets/stylesheets/bootstrap/_popovers.scss#L47 3 | // https://github.com/twbs/bootstrap-sass/blob/master/assets/stylesheets/bootstrap/_popovers.scss#L13 4 | margin: -10px -15px 5 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_list-group.sass: -------------------------------------------------------------------------------- 1 | .list-group-unbordered > .list-group-item 2 | // remove all but the top border from each list group item 3 | border-left: 0 4 | border-right: 0 5 | border-bottom: 0 6 | &:first-child 7 | // no top border on the first item either 8 | border-top: 0 9 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dancerfly_project.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /brambling/templatetags/brambling_forms.py: -------------------------------------------------------------------------------- 1 | # encoding: utf8 2 | from __future__ import absolute_import, division, unicode_literals 3 | 4 | from django import template 5 | 6 | 7 | register = template.Library() 8 | 9 | 10 | @register.filter 11 | def get_value(form, field): 12 | return form[field].value() 13 | -------------------------------------------------------------------------------- /brambling/templates/brambling/_stripe_note.html: -------------------------------------------------------------------------------- 1 |

2 | Credit card processing is handled securely by Stripe. Credit card data is managed by Stripe’s high-security servers. Read more about Stripe’s security. 3 |

4 | -------------------------------------------------------------------------------- /brambling/templates/brambling/layouts/12_sm.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/__base.html" %} 2 | 3 | {% block content %} 4 |
5 | {% block messages %} 6 | {% include "brambling/layouts/_messages.html" %} 7 | {% endblock %} 8 | {% block main %}{% endblock %} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /brambling/templates/brambling/layouts/12_xs.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/__base.html" %} 2 | 3 | {% block content %} 4 |
5 | {% block messages %} 6 | {% include "brambling/layouts/_messages.html" %} 7 | {% endblock %} 8 | {% block main %}{% endblock %} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /brambling/config.rb: -------------------------------------------------------------------------------- 1 | # Configuration file for Compass 2 | 3 | require 'bootstrap-sass' 4 | css_dir = "static/brambling/css" 5 | sass_dir = "sass" 6 | images_dir = "static/brambling/images" 7 | javascripts_dir = "static/brambling/js" 8 | fonts_dir = "static/brambling/fonts" 9 | relative_assets = true 10 | line_comments = false 11 | preferred_syntax = :sass 12 | -------------------------------------------------------------------------------- /brambling/templates/brambling/layouts/12.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/__base.html" %} 2 | 3 | {% block content %} 4 |
5 | {% block before %}{% endblock %} 6 | {% block messages %} 7 | {% include "brambling/layouts/_messages.html" %} 8 | {% endblock %} 9 | {% block main %}{% endblock %} 10 | {% block after %}{% endblock %} 11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_event/body_plaintext.txt: -------------------------------------------------------------------------------- 1 | You've been invited to attend {{ content.name }}! 2 | {% url "brambling_invite_accept" code=invite.code as url %} 3 | Use the following URL to accept your invitation: 4 | {{ protocol }}://{{ site.domain }}{{ url }} 5 | 6 | Happy dancing! 7 | 8 | The Dancerfly Team 9 | 10 | 11 | Dancerfly (dancerfly.com) is a project of Harris Lapiroff 12 | -------------------------------------------------------------------------------- /brambling/payment/core.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal, ROUND_DOWN 2 | 3 | TEST = 'test' 4 | LIVE = 'live' 5 | 6 | 7 | class InvalidAmountException(ValueError): 8 | """The amount to be charged must be zero or positive.""" 9 | 10 | 11 | def get_fee(event, amount): 12 | fee = event.application_fee_percent / 100 * Decimal(str(amount)) 13 | return fee.quantize(Decimal('0.01'), rounding=ROUND_DOWN) 14 | -------------------------------------------------------------------------------- /brambling/templatetags/brambling_plaintext.py: -------------------------------------------------------------------------------- 1 | # encoding: utf8 2 | from __future__ import absolute_import, division, unicode_literals 3 | 4 | from django import template 5 | 6 | 7 | register = template.Library() 8 | 9 | 10 | @register.filter 11 | def ljust(string, width, fillchar=' '): 12 | try: 13 | return string.ljust(width, fillchar) 14 | except (AttributeError, TypeError): 15 | return '' 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig for Little Weaver Web Collective Style 2 | # http://editorconfig.org/ http://littleweaverweb.com/ 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{html,sass,js,scss,css,php}] 12 | charset = utf-8 13 | indent_style = tab 14 | 15 | # PEP 8 Compliant 16 | [*.py] 17 | indent_style = space 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /brambling/templates/brambling/layouts/_messages.html: -------------------------------------------------------------------------------- 1 | {% if messages %} 2 | {% for message in messages %} 3 |
4 | 5 | {{ message }} 6 |
7 | {% endfor %} 8 | {% endif %} 9 | -------------------------------------------------------------------------------- /brambling/templates/registration/password_reset_complete.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/12_xs.html' %} 2 | {% load i18n %} 3 | 4 | {% block title %}{{ title }} – {{ block.super }}{% endblock %} 5 | {% block main %} 6 |

{{title}}

7 | 8 |

{% trans "Your password has been set. You may go ahead and log in now." %}

9 | 10 |

{% trans 'Log in' %}

11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/_nav_item.html: -------------------------------------------------------------------------------- 1 | {% url url_name event_slug=event.slug organization_slug=event.organization.slug as item_url %} 2 | {% with url_len=item_url|length|stringformat:"i" %} 3 | 4 | {% if icon %} 5 | 6 | {% endif %} 7 | {{ name }} 8 | 9 | {% endwith %} 10 | -------------------------------------------------------------------------------- /brambling/templates/brambling/forms/signup_row.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/forms/rows/bootstrap.html" %} 2 | 3 | {% load floppyforms %} 4 | 5 | {% block field_classes %}{% if field.errors %} has-error{% endif %}{% endblock %} 6 | 7 | {% block label %} 8 | {% if field|id %}{% endif %} 11 | {% endblock %} 12 | 13 | {% block help_text %}{% endblock %} 14 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_base.sass: -------------------------------------------------------------------------------- 1 | // Styles for HTML elements without classes 2 | 3 | html, body 4 | overflow-x: hidden 5 | padding: 0 6 | 7 | html 8 | height: 100% 9 | 10 | body 11 | min-height: 100% 12 | padding-top: $navbar-height + $line-height-computed 13 | &.no-navbar 14 | padding-top: 0 15 | 16 | mark 17 | background-color: rgba($blue-light, .5) 18 | padding: 0 2px 19 | margin: 0 -2px 20 | border-radius: 2px 21 | color: black 22 | -------------------------------------------------------------------------------- /brambling/templates/brambling/forms/markdown/field.html: -------------------------------------------------------------------------------- 1 | {% load zenaida cache %} 2 | 11 | -------------------------------------------------------------------------------- /brambling/migrations/0023_remove_person_dance_styles.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0022_auto_20150908_1533'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='person', 16 | name='dance_styles', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /brambling/templates/registration/logged_out.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/12.html' %} 2 | 3 | {% block title %}Logged out – {{ block.super }}{% endblock %} 4 | 5 | {% block meta %} 6 | {{ block.super }} 7 | 8 | {% endblock %} 9 | 10 | {% block main %} 11 |
12 | K, goodbye! You'll be redirected to the home page shortly. 13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /brambling/migrations/0029_remove_person_modified_directly.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0028_auto_20151003_2301'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='person', 16 | name='modified_directly', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /brambling/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.sites.shortcuts import get_current_site 3 | 4 | 5 | def google_analytics(request): 6 | return { 7 | 'GOOGLE_ANALYTICS_UA': getattr(settings, 'GOOGLE_ANALYTICS_UA', None), 8 | 'GOOGLE_ANALYTICS_DOMAIN': getattr(settings, 'GOOGLE_ANALYTICS_DOMAIN', None), 9 | } 10 | 11 | 12 | def current_site(request): 13 | return { 14 | 'site': get_current_site(request), 15 | } 16 | -------------------------------------------------------------------------------- /brambling/migrations/0014_remove_attendee_person_confirmed.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0013_auto_20150605_1920'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='attendee', 16 | name='person_confirmed', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /brambling/templates/registration/password_reset_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/12_xs.html' %} 2 | 3 | {% load floppyforms %} 4 | 5 | {% block title %}{{ title }} – {{ block.super }}{% endblock %} 6 | {% block main %} 7 |

{{title}}

8 | 9 |
10 | {% csrf_token %} 11 | {% form form %} 12 | 13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /dancerfly_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for dancerfly_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | 15 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dancerfly_project.settings") 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /brambling/migrations/0019_auto_20150528_1850.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0018_auto_20150528_1846'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterUniqueTogether( 15 | name='boughtitemdiscount', 16 | unique_together=set([('bought_item', 'code')]), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /brambling/utils/international.py: -------------------------------------------------------------------------------- 1 | from localflavor.us.forms import USZipCodeField 2 | from localflavor.gb.forms import GBPostcodeField 3 | 4 | 5 | def clean_postal_code(country, code): 6 | if not code: 7 | # Other places are responsible for checking whether empty is okay. 8 | return code 9 | if country == 'GB': 10 | field = GBPostcodeField() 11 | elif country == 'US': 12 | field = USZipCodeField() 13 | else: 14 | return code 15 | return field.clean(code) 16 | -------------------------------------------------------------------------------- /brambling/migrations/0006_remove_organization_check_postmark_cutoff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0005_copy_check_postmark_cutoff'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='organization', 16 | name='check_postmark_cutoff', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /brambling/templates/registration/sign_up.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/12_xs.html' %} 2 | 3 | {% load floppyforms %} 4 | 5 | {% block title %}Sign up – {{ block.super }}{% endblock %} 6 | {% block main %} 7 |

Sign up

8 | 9 |
10 | {% csrf_token %} 11 | {% form form using "brambling/forms/signup.html" %} 12 | 13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /brambling/templates/418.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/12.html' %} 2 | 3 | {% block title %}418 Error – {{ block.super }}{% endblock %} 4 | 5 | {% block main %} 6 |
7 |

Error 418: I am a teapot.

8 |

Short and stout and unable to brew coffee.

9 |

See HTCPCP-TEA §2.3.3 or Wikipedia.

10 |
11 | {% endblock main %} 12 | 13 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/orders.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/event/organizer/__table.html' %} 2 | 3 | {% load floppyforms zenaida %} 4 | 5 | {% block title %}Orders – {{ block.super }}{% endblock %} 6 | 7 | {% block cell %} 8 | {% if cell.field == 'code' %} 9 | {{ cell }} 10 | {% else %} 11 | {{ block.super }} 12 | {% endif %} 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_carousel.sass: -------------------------------------------------------------------------------- 1 | .carousel-inner 2 | background-color: black 3 | > .item > img 4 | margin: 0 auto 5 | 6 | 7 | .carousel-control 8 | > .fa-chevron-left, > .fa-chevron-right 9 | position: absolute 10 | top: 50% 11 | z-index: 5 12 | display: inline-block 13 | 14 | width: 20px 15 | height: 20px 16 | margin-top: -10px 17 | 18 | > .fa-chevron-left 19 | left: 50% 20 | margin-left: -10px 21 | 22 | 23 | > .fa-chevron-right 24 | right: 50% 25 | margin-right: -10px 26 | 27 | -------------------------------------------------------------------------------- /brambling/templates/registration/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/12_xs.html' %} 2 | {% load i18n %} 3 | 4 | {% block title %}{{ title }} – {{ block.super }}{% endblock %} 5 | {% block main %} 6 |

{{title}}

7 | 8 |

{% trans "We've emailed you instructions for setting your password. You should be receiving them shortly." %}

9 |

{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}

10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/attendees.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/event/organizer/__table.html' %} 2 | 3 | {% load floppyforms zenaida static %} 4 | 5 | {% block title %}Attendees – {{ block.super }}{% endblock %} 6 | 7 | {% block cell %} 8 | {% if cell.field == 'order_code' %} 9 | {{ cell }} 10 | {% else %} 11 | {{ block.super }} 12 | {% endif %} 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /brambling/templates/brambling/layouts/11_1.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/__base.html" %} 2 | 3 | {% block content %} 4 |
5 | {% block before %}{% endblock %} 6 |
7 |
8 | {% block messages %} 9 | {% include "brambling/layouts/_messages.html" %} 10 | {% endblock %} 11 | 12 | {% block main %}{% endblock %} 13 |
14 |
{% block side %}{% endblock %}
15 |
16 | {% block after %}{% endblock %} 17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /brambling/templates/brambling/layouts/3_9.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/__base.html" %} 2 | 3 | {% block content %} 4 |
5 | {% block before %}{% endblock %} 6 |
7 |
{% block side %}{% endblock %}
8 |
9 | {% block messages %} 10 | {% include "brambling/layouts/_messages.html" %} 11 | {% endblock %} 12 | 13 | {% block main %}{% endblock %} 14 |
15 |
16 | {% block after %}{% endblock %} 17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /brambling/templates/brambling/layouts/6_6.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/__base.html" %} 2 | 3 | {% block content %} 4 |
5 | {% block before %}{% endblock %} 6 |
7 |
8 | {% block messages %} 9 | {% include "brambling/layouts/_messages.html" %} 10 | {% endblock %} 11 | 12 | {% block main %}{% endblock %} 13 |
14 |
{% block side %}{% endblock %}
15 |
16 | {% block after %}{% endblock %} 17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /brambling/migrations/0033_attendee_notes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0032_auto_20151010_0624'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='attendee', 16 | name='notes', 17 | field=models.TextField(blank=True), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /brambling/migrations/0048_auto_20160204_0314.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0047_auto_20160131_0110'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='invite', 16 | name='kind', 17 | field=models.CharField(max_length=10), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /brambling/templates/brambling/forms/_top_error_list.html: -------------------------------------------------------------------------------- 1 | {% if form.errors %} 2 |
3 | Your form was not successfully submitted. 4 | {% if form.non_field_errors %} 5 | Please correct the following errors: 6 | 11 | {% else %} 12 | {# If there are no non-field errors, assume there are field errors below. #} 13 | Please correct the highlighted fields below. 14 | {% endif %} 15 |
16 | {% endif %} 17 | -------------------------------------------------------------------------------- /brambling/migrations/0032_auto_20151010_0624.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0031_auto_20151010_0621'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='person', 16 | name='dietary_restrictions', 17 | ), 18 | migrations.DeleteModel( 19 | name='DietaryRestriction', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/email_confirm/body_plaintext.txt: -------------------------------------------------------------------------------- 1 | Thanks for signing up for Dancerfly, {{ person.first_name }}! 2 | {% url 'brambling_email_confirm' pkb64=pkb64 token=token as url %} 3 | Please use the following URL to verify your email address! 4 | {{ protocol }}://{{ site.domain }}{{ url }} 5 | 6 | Happy dancing! 7 | 8 | The Dancerfly Team 9 | 10 | P.S. If you didn't register for Dancerfly or believe this message was intended for someone else, please email support@dancerfly.com. 11 | 12 | 13 | Dancerfly (dancerfly.com) is a project of Harris Lapiroff 14 | -------------------------------------------------------------------------------- /brambling/templates/brambling/invites/transfer.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/invites/__base.html' %} 2 | 3 | {% block invite_title %} 4 | You've been offered an item transfer! 5 | {% endblock %} 6 | 7 | {% block before %} 8 |

{% with event=content.order.event %} 9 | You've been sent a transfer of the following item for 10 | {{ event.name }}: 11 | {{ content.item_option_name }} ({{ content.item_name}}). 12 | {% endwith %}

13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /brambling/templates/brambling/layouts/8_4.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/__base.html" %} 2 | 3 | {% block content %} 4 |
5 | {% block before %}{% endblock %} 6 |
7 |
{% block side %}{% endblock %}
8 |
9 | {% block messages %} 10 | {% include "brambling/layouts/_messages.html" %} 11 | {% endblock %} 12 | 13 | {% block main %}{% endblock %} 14 |
15 |
16 | {% block after %}{% endblock %} 17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /brambling/templates/brambling/layouts/9_3.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/__base.html" %} 2 | 3 | {% block content %} 4 |
5 | {% block before %}{% endblock %} 6 |
7 |
{% block side %}{% endblock %}
8 |
9 | {% block messages %} 10 | {% include "brambling/layouts/_messages.html" %} 11 | {% endblock %} 12 | 13 | {% block main %}{% endblock %} 14 |
15 |
16 | {% block after %}{% endblock %} 17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /brambling/migrations/0004_event_check_postmark_cutoff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0003_auto_20150514_2049'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='event', 16 | name='check_postmark_cutoff', 17 | field=models.DateField(null=True, blank=True), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /brambling/migrations/0012_customformfield_help_text.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0011_auto_20150603_1708'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='customformfield', 16 | name='help_text', 17 | field=models.CharField(max_length=255, blank=True), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /brambling/templates/brambling/_footer.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /brambling/migrations/0010_auto_20150528_1805.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0009_set_stripe_metadata'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='item', 16 | name='name', 17 | field=models.CharField(help_text='Full pass, dance-only pass, T-shirt, socks, etc.', max_length=30), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /brambling/migrations/0053_auto_20160425_2258.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0052_auto_20160318_0712'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='customform', 16 | name='form_type', 17 | field=models.CharField(max_length=8, choices=[('attendee', 'Attendee'), ('order', 'Order'), ('housing', 'Housing'), ('hosting', 'Hosting')]), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_menus.sass: -------------------------------------------------------------------------------- 1 | .dropdown-menu > li > a 2 | padding: 3px #{$line-height-computed/2} 3 | 4 | 5 | //https://github.com/twbs/bootstrap-sass/blob/master/assets/stylesheets/bootstrap/_dropdowns.scss#L65 6 | .dropdown-menu > li > .btn-link 7 | display: block 8 | padding: 3px 20px 9 | clear: both 10 | font-weight: normal 11 | line-height: $line-height-base 12 | color: $dropdown-link-color 13 | white-space: nowrap 14 | width: 100% 15 | text-align: left 16 | 17 | &:hover, &:focus 18 | text-decoration: none 19 | color: $dropdown-link-hover-color 20 | background-color: $dropdown-link-hover-bg 21 | -------------------------------------------------------------------------------- /brambling/migrations/0003_auto_20150514_2049.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0002_auto_20150502_0912'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='invite', 16 | name='kind', 17 | field=models.CharField(max_length=10, choices=[('editor', 'Event Editor'), ('org_editor', 'Organization Editor')]), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/order/_completed_transfers_summary.html: -------------------------------------------------------------------------------- 1 | {% if transferred_items %} 2 |
3 |
4 |
5 | Completed Transfers 6 |
7 |
8 | 9 | 20 |
21 | {% endif %} 22 | -------------------------------------------------------------------------------- /brambling/api/v1/endpoints/dancestyle.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers, viewsets 2 | 3 | from brambling.api.v1.permissions import IsAdminUserOrReadOnly 4 | from brambling.models import DanceStyle 5 | 6 | 7 | class DanceStyleSerializer(serializers.ModelSerializer): 8 | link = serializers.HyperlinkedIdentityField(view_name='dancestyle-detail') 9 | 10 | class Meta: 11 | model = DanceStyle 12 | fields = ('link', 'name',) 13 | 14 | 15 | class DanceStyleViewSet(viewsets.ModelViewSet): 16 | queryset = DanceStyle.objects.all() 17 | serializer_class = DanceStyleSerializer 18 | permission_classes = [IsAdminUserOrReadOnly] 19 | -------------------------------------------------------------------------------- /brambling/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/12_xs.html' %} 2 | 3 | {% load floppyforms %} 4 | 5 | {% block title %}Log in – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 |

Log in

9 | 10 |
11 | {% csrf_token %} 12 | {% form form %} 13 | 14 | Sign up 15 | Forgot your password? 16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /brambling/templates/brambling/__base_xl.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/__base.html" %} 2 | 3 | {% block body %} 4 | 5 | {% block top %} 6 | {% include "brambling/_navbar.html" with container_class="container-xl" %} 7 | {% endblock top %} 8 | 9 |
10 | 13 | {% block content %}{% endblock %} 14 |
15 | 16 | {% block bottom %} 17 | {% include "brambling/_footer.html" %} 18 | {% endblock %} 19 | 20 | {% endblock body %} 21 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/__narrow.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/event/organizer/__base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 | {% include "brambling/event/organizer/_nav.html" %} 7 |
8 | {# Narrower column that doesn't span the whole width of the page. #} 9 |
10 | {% block messages %} 11 | {% include "brambling/layouts/_messages.html" %} 12 | {% endblock %} 13 | {% block main %}{% endblock %} 14 |
15 |
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /brambling/templates/brambling/forms/hosting.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/forms/home.html" %} 2 | 3 | {% load floppyforms %} 4 | 5 | {% block rows %} 6 | {% formconfig row using "brambling/forms/rows/bootstrap.html" %} 7 | 8 |
9 |
10 |

House Contact

11 |
12 |
13 | {% formrow form.contact_name %} 14 | {% formrow form.contact_email %} 15 | {% formrow form.contact_phone %} 16 |
17 |
18 | 19 | {{ block.super }} 20 | 21 | {% formrow form.other_needs %} 22 | 23 | {% formrow form.save_as_defaults %} 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /brambling/templates/brambling/__base_fluid.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/__base.html" %} 2 | 3 | {% block body %} 4 | 5 | {% block top %} 6 | {% include "brambling/_navbar.html" with container_class="container-fluid" %} 7 | {% endblock top %} 8 | 9 |
10 | 13 | {% block content %}{% endblock %} 14 |
15 | 16 | {% block bottom %} 17 | {% include "brambling/_footer.html" %} 18 | {% endblock %} 19 | 20 | {% endblock body %} 21 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | postgresql: 4 | image: 'postgres:10.5-alpine' 5 | environment: 6 | POSTGRES_USER: dancerfly 7 | POSTGRES_PASSWORD: dancerflypassword 8 | POSTGRES_DB: dancerfly 9 | ports: 10 | - "127.0.0.1::5432" 11 | django: 12 | stdin_open: true 13 | tty: true 14 | ports: 15 | - "127.0.0.1:8000:8000" 16 | build: . 17 | environment: 18 | DATABASE_URL: postgres://dancerfly:dancerflypassword@postgresql/dancerfly 19 | volumes: 20 | - ./:/django 21 | depends_on: 22 | - postgresql 23 | -------------------------------------------------------------------------------- /brambling/api/v1/endpoints/housingcategory.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers, viewsets 2 | 3 | from brambling.api.v1.permissions import IsAdminUserOrReadOnly 4 | from brambling.models import HousingCategory 5 | 6 | 7 | class HousingCategorySerializer(serializers.ModelSerializer): 8 | link = serializers.HyperlinkedIdentityField(view_name='housingcategory-detail') 9 | 10 | class Meta: 11 | model = HousingCategory 12 | fields = ('link', 'name',) 13 | 14 | 15 | class HousingCategoryViewSet(viewsets.ModelViewSet): 16 | queryset = HousingCategory.objects.all() 17 | serializer_class = HousingCategorySerializer 18 | permission_classes = [IsAdminUserOrReadOnly] 19 | -------------------------------------------------------------------------------- /brambling/migrations/0005_copy_check_postmark_cutoff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | def check_postmark_cutoff(apps, schema_editor): 8 | Event = apps.get_model('brambling', 'Event') 9 | for event in Event.objects.select_related('organization'): 10 | event.check_postmark_cutoff = event.organization.check_postmark_cutoff 11 | event.save() 12 | 13 | 14 | class Migration(migrations.Migration): 15 | 16 | dependencies = [ 17 | ('brambling', '0004_event_check_postmark_cutoff'), 18 | ] 19 | 20 | operations = [ 21 | migrations.RunPython(check_postmark_cutoff), 22 | ] 23 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_transfer/body_plaintext.txt: -------------------------------------------------------------------------------- 1 | {% autoescape off %}You've been offered an item transfer! 2 | {% with event=content.order.event %} 3 | {% if invite.user %}{{ invite.user.first_name }}{% else %}{{ content.order.email }}{% endif %} wants to transfer the folowing item to you for {{ event.name }}: {{ content.item_option_name }} ({{ content.item_name }})! 4 | {% endwith %} 5 | {% url "brambling_invite_accept" code=invite.code as url %} 6 | Use the following URL to accept your invitation: 7 | {{ protocol }}://{{ site.domain }}{{ url }} 8 | 9 | Happy dancing! 10 | 11 | The Dancerfly Team 12 | 13 | 14 | Dancerfly (dancerfly.com) is a project of Harris Lapiroff 15 | {% endautoescape %} 16 | -------------------------------------------------------------------------------- /brambling/migrations/0025_auto_20151002_2116.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0024_auto_20150924_1842'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='boughtitem', 16 | name='status', 17 | field=models.CharField(default='unpaid', max_length=11, choices=[('reserved', 'Reserved'), ('unpaid', 'Unpaid'), ('bought', 'Bought'), ('refunded', 'Refunded'), ('transferred', 'Transferred')]), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_headings.sass: -------------------------------------------------------------------------------- 1 | .page-header 2 | // Remove Bootstrap's top margin from .page-header 3 | &, & > h1, & > h2, & > h3, & > h4, & > h5, & > h6 4 | margin-top: 0 5 | 6 | .header-divider 7 | font-size: $font-size-base * .9 8 | line-height: $line-height-computed 9 | text-align: center 10 | clear: both 11 | span 12 | background-color: #FFF 13 | text-transform: uppercase 14 | letter-spacing: .02em 15 | padding: 0 .5em 16 | z-index: 1 17 | position: relative 18 | color: #888 19 | &::after 20 | border-bottom: 1px solid #CCC 21 | display: block 22 | content: " " 23 | height: 0 24 | position: absolute 25 | top: 50% 26 | left: 0 27 | right: 0 28 | z-index: 0 29 | -------------------------------------------------------------------------------- /brambling/migrations/0049_auto_20160213_0401.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0048_auto_20160204_0314'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='event', 16 | name='additional_editors', 17 | ), 18 | migrations.RemoveField( 19 | model_name='organization', 20 | name='editors', 21 | ), 22 | migrations.RemoveField( 23 | model_name='organization', 24 | name='owner', 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /brambling/api/v1/endpoints/environmentalfactor.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers, viewsets 2 | 3 | from brambling.api.v1.permissions import IsAdminUserOrReadOnly 4 | from brambling.models import EnvironmentalFactor 5 | 6 | 7 | class EnvironmentalFactorSerializer(serializers.ModelSerializer): 8 | link = serializers.HyperlinkedIdentityField(view_name='environmentalfactor-detail') 9 | 10 | class Meta: 11 | model = EnvironmentalFactor 12 | fields = ('link', 'name',) 13 | 14 | 15 | class EnvironmentalFactorViewSet(viewsets.ModelViewSet): 16 | queryset = EnvironmentalFactor.objects.all() 17 | serializer_class = EnvironmentalFactorSerializer 18 | permission_classes = [IsAdminUserOrReadOnly] 19 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_event_view/body_plaintext.txt: -------------------------------------------------------------------------------- 1 | You've been invited to help edit an event on Dancerfly! 2 | 3 | {{ invite.user.first_name }} is running their event, {{ content.name }}, on dancerfly.com, 4 | and added you to their team as a read-only member! That means you have access to 5 | this event's data, without having access to change the event's configuration 6 | or modify anyone's orders. 7 | 8 | Ready to get cracking? 9 | {% url "brambling_invite_accept" code=invite.code as url %} 10 | Use the following URL to accept your invitation: 11 | {{ protocol }}://{{ site.domain }}{{ url }} 12 | 13 | Happy editing! 14 | 15 | The Dancerfly Team 16 | 17 | 18 | Dancerfly (dancerfly.com) is a project of Harris Lapiroff 19 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_event_edit/body_plaintext.txt: -------------------------------------------------------------------------------- 1 | You've been invited to help edit an event on Dancerfly! 2 | 3 | {{ invite.user.first_name }} is running their event, {{ content.name }}, on dancerfly.com, 4 | and added you as an editor! That means you have access to all the 5 | information and tools you need to give them a hand setting up 6 | and running this event. The power... THE POWER! ...ahem. 7 | 8 | Ready to get cracking? 9 | {% url "brambling_invite_accept" code=invite.code as url %} 10 | Use the following URL to accept your invitation: 11 | {{ protocol }}://{{ site.domain }}{{ url }} 12 | 13 | Happy editing! 14 | 15 | The Dancerfly Team 16 | 17 | 18 | Dancerfly (dancerfly.com) is a project of Harris Lapiroff 19 | -------------------------------------------------------------------------------- /brambling/templates/brambling/organization/__admin.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/__base_fluid.html' %} 2 | 3 | {% block title %}{{ organization.name }} – {{ block.super }}{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 | {% include "brambling/organization/_nav.html" %} 9 |
10 | {# Narrower column that doesn't span the whole width of the page. #} 11 |
12 | {% block messages %} 13 | {% include "brambling/layouts/_messages.html" %} 14 | {% endblock %} 15 | {% block main %}{% endblock %} 16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /brambling/migrations/0054_processedstripeevent.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0053_auto_20160425_2258'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='ProcessedStripeEvent', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('stripe_event_id', models.CharField(max_length=35)), 19 | ('created', models.DateTimeField(auto_now_add=True)), 20 | ], 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /brambling/migrations/0024_auto_20150924_1842.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0023_remove_person_dance_styles'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='customform', 16 | name='form_type', 17 | field=models.CharField(help_text='Order forms will only display if "collect survey data" is checked in your event settings', max_length=8, choices=[('attendee', 'Attendee'), ('order', 'Order'), ('housing', 'Housing'), ('hosting', 'Hosting')]), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /brambling/templates/brambling/_stripe_form.html: -------------------------------------------------------------------------------- 1 | {% if api_type == 'test' %} 2 |
3 |

This event is in test mode; use these test credit card numbers to complete your purchase.

4 |
5 | {% elif not request.is_secure %} 6 |
7 |

Warning: This connection is not secure. Credit card processing is disabled. Please contact a system administrator if you think this message is in error.

8 |
9 | {% endif %} 10 | 11 |
12 | 13 |
14 |
15 | {% include 'brambling/_stripe_note.html' %} 16 | -------------------------------------------------------------------------------- /brambling/templates/brambling/user/home.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/user/__base.html' %} 2 | 3 | {% load floppyforms %} 4 | 5 | {% block title %}Home settings – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 | {{ block.super }} 9 |

Home settings

10 | 11 |

Please include the preferences of all of the residents of your home when filling out this form.

12 | 13 | {% include "brambling/forms/_top_error_list.html" %} 14 | 15 |
16 | {% csrf_token %} 17 | {% form form using "brambling/forms/home.html" %} 18 | 19 | Cancel 20 |
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/_empty_shop_alert.html: -------------------------------------------------------------------------------- 1 | {% if 'edit' in event_permissions %} 2 | {% if event.items.count == 0 %} 3 | {# If there are no items at all: #} 4 |
5 |

6 | Hey, event organizer! Your event doesn’t have any items available for sale. That’s fine if registration isn't open yet, but if you want people to start registering for the event, why not head over to the store to create some passes? 7 | Manage items 8 |

9 |
10 | {% endif %} 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_org_edit/body_plaintext.txt: -------------------------------------------------------------------------------- 1 | You've been invited to help manage an organization on Dancerfly! 2 | 3 | {{ invite.user.first_name }} is running events for their organization, {{ content.name }}, on dancerfly.com, 4 | and added you as an editor! That means you have access to all the 5 | information and tools you need to give them a hand setting up 6 | and running events for this organization. The power... THE POWER! ...ahem. 7 | 8 | Ready to get cracking? 9 | {% url "brambling_invite_accept" code=invite.code as url %} 10 | Use the following URL to accept your invitation: 11 | {{ protocol }}://{{ site.domain }}{{ url }} 12 | 13 | Happy editing! 14 | 15 | The Dancerfly Team 16 | 17 | 18 | Dancerfly (dancerfly.com) is a project of Harris Lapiroff 19 | 20 | -------------------------------------------------------------------------------- /brambling/migrations/0036_auto_20151219_0132.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from decimal import Decimal 6 | import django.core.validators 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('brambling', '0035_auto_20151202_1727'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='event', 18 | name='application_fee_percent', 19 | field=models.DecimalField(default=Decimal('1.5'), max_digits=5, decimal_places=2, validators=[django.core.validators.MaxValueValidator(100), django.core.validators.MinValueValidator(0)]), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /dancerfly_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import patterns, include, url 3 | from django.contrib import admin 4 | 5 | urlpatterns = patterns( 6 | '', 7 | url(r'^grappelli/', include('grappelli.urls')), 8 | url(r'^admin/', include(admin.site.urls)), 9 | url(r'^talkback/', include('talkback.urls')), 10 | url(r'^', include('brambling.urls')), 11 | ) 12 | 13 | 14 | if settings.DEBUG: 15 | from django.conf.urls.static import static 16 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 17 | 18 | # Serve static and media files from development server 19 | urlpatterns = staticfiles_urlpatterns() + urlpatterns 20 | urlpatterns = static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + urlpatterns 21 | -------------------------------------------------------------------------------- /brambling/templates/brambling/_stripe_js.html: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/item_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/event/organizer/__narrow.html' %} 2 | 3 | {% load floppyforms static %} 4 | 5 | {% block title %}{% if item.pk %}Edit {{ item.name }}{% else %}Add an item{% endif %} – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 | {{ block.super }} 9 | 10 |

{% if item.pk %}{{ item.name }}{% else %}Add an item{% endif %}

11 | 12 | {% include "brambling/event/organizer/_item_form.html" with action=request.path %} 13 | {% endblock %} 14 | 15 | {% block javascripts %} 16 | {{ block.super }} 17 | {% if 'edit' in event_permissions %} 18 | {% include "brambling/event/organizer/_item_form_js.html" %} 19 | {% else %} 20 | {% include "brambling/_disable_forms_js.html" %} 21 | {% endif %} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_org_view/body_plaintext.txt: -------------------------------------------------------------------------------- 1 | You've been invited to help manage an organization on Dancerfly! 2 | 3 | {{ invite.user.first_name }} is running events for their organization, {{ content.name }}, on dancerfly.com, 4 | and added you to their team as a read-only member! That means you have access to all the 5 | data related to the organization's events, without having access to change the event's configuration 6 | or modify anyone's orders. 7 | 8 | Ready to get cracking? 9 | {% url "brambling_invite_accept" code=invite.code as url %} 10 | Use the following URL to accept your invitation: 11 | {{ protocol }}://{{ site.domain }}{{ url }} 12 | 13 | Happy editing! 14 | 15 | The Dancerfly Team 16 | 17 | 18 | Dancerfly (dancerfly.com) is a project of Harris Lapiroff 19 | 20 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/order/_steps_ractive.html: -------------------------------------------------------------------------------- 1 | {# This whole template is verbatim - use ractive, not Django. #} 2 | {% verbatim %} 3 | {{#if steps }} 4 |
5 | {{#steps}} 6 | {{#if key == activeStep}} 7 |
{{ name }}
8 | {{elseif is_accessible && is_completed}} 9 | 10 | 11 | {{ name }} 12 | 13 | {{elseif is_accessible}} 14 | 15 | {{ name }} 16 | 17 | {{else}} 18 |
{{ name }}
19 | {{/if}} 20 | {{/steps}} 21 |
22 | {{/if}} 23 | {% endverbatim %} 24 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/order/email.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/event/order/__base.html' %} 2 | 3 | {% load floppyforms %} 4 | 5 | {% block title %}Email receipt – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 | {{ block.super }} 9 | 10 |
11 | {% csrf_token %} 12 | 13 |
14 | {% form form using %} 15 | {% formconfig row using "brambling/forms/rows/bootstrap.html" %} 16 | 17 |

Please enter your email address to receive a receipt and link to your order.

18 | 19 | {% formrow form.email %} 20 | {% endform %} 21 |
22 | 23 | 24 |
25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /brambling/migrations/0030_auto_20151009_0631.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('brambling', '0029_remove_person_modified_directly'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='attendee', 17 | name='person', 18 | ), 19 | migrations.AddField( 20 | model_name='attendee', 21 | name='saved_attendee', 22 | field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='brambling.SavedAttendee', null=True), 23 | preserve_default=True, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /brambling/templates/registration/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/12_xs.html' %} 2 | 3 | {% load floppyforms i18n %} 4 | 5 | {% block title %}{{ title }} – {{ block.super }}{% endblock %} 6 | {% block main %} 7 |

{{title}}

8 | 9 | {% if validlink %} 10 |

{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}

11 | 12 |
13 | {% csrf_token %} 14 | {% form form %} 15 | 16 |
17 | {% else %} 18 |

{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}

19 | {% endif %} 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/discount_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/event/organizer/__narrow.html' %} 2 | 3 | {% load floppyforms %} 4 | 5 | {% block title %}{% if discount.pk %}Edit {{ discount.name }}{% else %}Add a discount{% endif %} – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 | {{ block.super }} 9 | 10 |

{% if discount.pk %}Edit discount: {{ discount.name }}{% else %}Add a discount{% endif %}

11 | 12 | {# novalidate - see https://github.com/brutasse/django-floppyforms/issues/75 #} 13 | {% include "brambling/event/organizer/_discount_form.html" with action=request.path %} 14 | {% endblock %} 15 | 16 | {% block javascripts %} 17 | {{ block.super }} 18 | {% if not 'edit' in event_permissions %} 19 | {% include "brambling/_disable_forms_js.html" %} 20 | {% endif %} 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/invite_org_owner/body_plaintext.txt: -------------------------------------------------------------------------------- 1 | You've been invited to help manage an organization on Dancerfly! 2 | 3 | {{ invite.user.first_name }} is running events for their organization, {{ content.name }}, on dancerfly.com, 4 | and added you as an owner! That means you have access to all the 5 | information and tools you need to give them a hand setting up 6 | and running events for this organization, including the ability to 7 | add new members to your team. The power... THE POWER! ...ahem. 8 | 9 | Ready to get cracking? 10 | {% url "brambling_invite_accept" code=invite.code as url %} 11 | Use the following URL to accept your invitation: 12 | {{ protocol }}://{{ site.domain }}{{ url }} 13 | 14 | Happy editing! 15 | 16 | The Dancerfly Team 17 | 18 | 19 | Dancerfly (dancerfly.com) is a project of Harris Lapiroff 20 | 21 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/order/_pending_transfers_summary.html: -------------------------------------------------------------------------------- 1 | {% if pending_transfers %} 2 |
3 |
4 |
5 | Pending Transfers 6 |
7 |
8 | 9 | 29 |
30 | {% endif %} 31 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_event-listing.sass: -------------------------------------------------------------------------------- 1 | .event-listing-image 2 | width: 100% 3 | // 300 / 940 is the height/width ratio of the banner image. 4 | // We use this technique to scale the size of the element 5 | // properly while still being responsive and avoiding 6 | // layout shifting as images load. 7 | padding-top: percentage(300 / 940) 8 | background-image: $dancers-event-banner-url 9 | background-size: cover 10 | background-position: center 11 | background-repeat: no-repeat 12 | box-shadow: inset 0 0 0 1px rgba(0,0,0,.2) 13 | box-sizing: border-box 14 | border-radius: $border-radius-base 15 | 16 | .event-listing 17 | & > .relative-right 18 | right: 6px 19 | & > .relative-top 20 | top: 6px 21 | 22 | & > h3 23 | margin-top: 10px 24 | margin-bottom: 5px 25 | 26 | & > h5 27 | margin-top: 0 28 | margin-bottom: 5px 29 | -------------------------------------------------------------------------------- /brambling/templates/brambling/forms/itemimage.html: -------------------------------------------------------------------------------- 1 | {% extends "floppyforms/layouts/bootstrap.html" %} 2 | 3 | {% load floppyforms %} 4 | 5 | {% block rows %} 6 |
7 | 8 | {% if not "__prefix__" in form.prefix %} 9 | {% formrow form.image with display_image=1 %} 10 | {% else %} 11 | {% formrow form.image with display_image=0 %} 12 | {% endif %} 13 | 14 | {% for field in form.hidden_fields %} 15 | {% formfield field %} 16 | {% endfor %} 17 | 18 | {# The following form fields are controlled with javascript: #} 19 |
20 | {% formrow form.order %} 21 | {# Don't render delete box if this is a dynamic form. We'll delete those by removing the DOM element with javascript. #} 22 | {% if not "__prefix__" in form.prefix %}{% formrow form.DELETE %}{% endif %} 23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /brambling/static/brambling/brambling.slug.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Brambling Slug 3 | * @author Harris Lapiroff 4 | * @requires jQuery, 5 | * Django Contrib Admin prepopulate.js, 6 | * Django Contrib Admin urlify.js 7 | * 8 | * This is very Brambling-specific and currently refers specifically to 9 | * Brambling-specific field names from the event create and event update forms. 10 | * 11 | * Copyright (c) 2020, Harris Lapiroff 12 | * All rights reserved. 13 | * 14 | * Licensed under the New BSD License 15 | * See: http://www.opensource.org/licenses/bsd-license.php 16 | * 17 | */ 18 | 19 | (function () { 20 | "use strict"; 21 | 22 | var FIELD_SELECTOR = '#id_slug', 23 | DEPENDENCY_SELECTORS = ['#id_name']; 24 | 25 | $(function () { 26 | $(FIELD_SELECTOR).prepopulate(DEPENDENCY_SELECTORS); 27 | }); 28 | }()); 29 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/danger_zone.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/event/organizer/__base.html' %} 2 | 3 | {% load zenaida static %} 4 | 5 | {% block title %}Copy Event – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 | {{ block.super }} 9 | 10 | {% url "brambling_event_attendees" event_slug=event.slug organization_slug=event.organization.slug as attendees_url%} 11 | 12 |

Copy event

13 |

Make a copy of this event with the same settings and items in any organization 14 | you control.

15 |
16 |
17 | 18 | Copy Event 19 | 20 |
21 |
22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /brambling/migrations/0057_auto_20190105_0626.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2019-01-05 06:26 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('brambling', '0056_auto_20170606_1844'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterUniqueTogether( 16 | name='orderdiscount', 17 | unique_together=set([]), 18 | ), 19 | migrations.RemoveField( 20 | model_name='orderdiscount', 21 | name='discount', 22 | ), 23 | migrations.RemoveField( 24 | model_name='orderdiscount', 25 | name='order', 26 | ), 27 | migrations.DeleteModel( 28 | name='OrderDiscount', 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /brambling/migrations/0022_auto_20150908_1533.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.core.validators 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('brambling', '0021_auto_20150903_2358'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='event', 17 | name='slug', 18 | field=models.SlugField(help_text='URL-friendly version of the event name. Dashes, 0-9, and lower-case a-z only.', validators=[django.core.validators.RegexValidator('^[a-z0-9-]+$')]), 19 | preserve_default=True, 20 | ), 21 | migrations.AlterUniqueTogether( 22 | name='event', 23 | unique_together=set([('slug', 'organization')]), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/order/_steps.html: -------------------------------------------------------------------------------- 1 |
2 | {% for step in workflow.active_steps %} 3 | {% url step.view_name organization_slug=event.organization.slug event_slug=event.slug as step_url %} 4 | 5 | {# a or div, depending on if the step is accessible #} 6 | {% if step == current_step %} 7 |
8 | {{ step.name }} 9 |
10 | {% elif step.is_completed %} 11 | 12 | 13 | {{ step.name }} 14 | 15 | {% elif step.is_accessible %} 16 | 17 | {{ step.name }} 18 | 19 | {% else %} 20 |
21 | {{ step.name }} 22 |
23 | {% endif %} 24 | {% endfor %} 25 |
26 | -------------------------------------------------------------------------------- /brambling/payment/stripe/core.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | import stripe 3 | 4 | from brambling.payment.core import LIVE 5 | 6 | 7 | def stripe_prep(api_type): 8 | stripe.api_version = '2016-03-07' 9 | if api_type == LIVE: 10 | stripe.api_key = settings.STRIPE_SECRET_KEY 11 | else: 12 | stripe.api_key = settings.STRIPE_TEST_SECRET_KEY 13 | 14 | 15 | def stripe_test_settings_valid(): 16 | return bool( 17 | getattr(settings, 'STRIPE_TEST_APPLICATION_ID', False) and 18 | getattr(settings, 'STRIPE_TEST_SECRET_KEY', False) and 19 | getattr(settings, 'STRIPE_TEST_PUBLISHABLE_KEY', False) 20 | ) 21 | 22 | 23 | def stripe_live_settings_valid(): 24 | return bool( 25 | getattr(settings, 'STRIPE_APPLICATION_ID', False) and 26 | getattr(settings, 'STRIPE_SECRET_KEY', False) and 27 | getattr(settings, 'STRIPE_PUBLISHABLE_KEY', False) 28 | ) 29 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_panels.sass: -------------------------------------------------------------------------------- 1 | .panel-title 2 | font-family: $headings-font-family 3 | font-weight: $headings-font-weight-bold 4 | font-size: $font-size-h4 5 | letter-spacing: .25px 6 | 7 | .panel-dark 8 | @extend .panel-default 9 | background-color: $panel-default-heading-bg 10 | 11 | .panel.hug 12 | border-top: 0 13 | 14 | .panel > .scroll-x > table 15 | margin-bottom: 0 16 | 17 | .panel-select-target 18 | position: relative 19 | 20 | &:before 21 | position: absolute 22 | display: block 23 | top: 0 24 | bottom: 0 25 | left: 0 26 | right: 0 27 | content: "" 28 | background: rgba(100, 100, 100, .9) 29 | z-index: 10 30 | border-radius: $border-radius-base 31 | 32 | .panel-select-target-text 33 | position: absolute 34 | display: block 35 | top: 50% 36 | transform: translateY(-50%) 37 | width: 100% 38 | padding: 40px 39 | text-align: center 40 | z-index: 11 41 | color: white 42 | font-weight: bold 43 | -------------------------------------------------------------------------------- /brambling/migrations/0016_auto_20150528_1808.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | def cache_boughtitem_values(apps, schema_editor): 8 | BoughtItem = apps.get_model('brambling', 'BoughtItem') 9 | 10 | for bought_item in BoughtItem.objects.select_related('item_option__item'): 11 | item_option = bought_item.item_option 12 | item = item_option.item 13 | 14 | bought_item.item_name = item.name 15 | bought_item.item_description = item.description 16 | bought_item.item_option_name = item_option.name 17 | bought_item.price = item_option.price 18 | bought_item.save() 19 | 20 | 21 | class Migration(migrations.Migration): 22 | 23 | dependencies = [ 24 | ('brambling', '0015_auto_20150528_1806'), 25 | ] 26 | 27 | operations = [ 28 | migrations.RunPython(cache_boughtitem_values, lambda *a, **k: None), 29 | ] 30 | -------------------------------------------------------------------------------- /brambling/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/12.html' %} 2 | 3 | {% load staticfiles %} 4 | 5 | {% block title %}404 Error – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 |
9 |
10 |

404 Not Found

11 |

Uh oh! No page was found at:

12 |

13 | 
14 | 		

But don't panic, try one of these:

15 | 20 |
21 |
22 | 23 | 24 | 25 | 28 | {% endblock main %} 29 | -------------------------------------------------------------------------------- /brambling/migrations/0028_auto_20151003_2301.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('brambling', '0027_saved_attendee_data'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='savedattendee', 18 | name='created', 19 | field=models.DateTimeField(default=datetime.datetime(2015, 10, 3, 23, 1, 14, 80632, tzinfo=utc), auto_now_add=True), 20 | preserve_default=False, 21 | ), 22 | migrations.AddField( 23 | model_name='savedattendee', 24 | name='last_modified', 25 | field=models.DateTimeField(default=datetime.datetime(2015, 10, 3, 23, 1, 18, 111276, tzinfo=utc), auto_now=True), 26 | preserve_default=False, 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /brambling/migrations/0018_auto_20150528_1846.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | def cache_boughtitemdiscount_values(apps, schema_editor): 8 | BoughtItemDiscount = apps.get_model('brambling', 'BoughtItemDiscount') 9 | 10 | for bought_item_discount in BoughtItemDiscount.objects.select_related('discount'): 11 | discount = bought_item_discount.discount 12 | 13 | bought_item_discount.name = discount.name 14 | bought_item_discount.code = discount.code 15 | bought_item_discount.discount_type = discount.discount_type 16 | bought_item_discount.amount = discount.amount 17 | bought_item_discount.save() 18 | 19 | 20 | class Migration(migrations.Migration): 21 | 22 | dependencies = [ 23 | ('brambling', '0017_auto_20150528_1846'), 24 | ] 25 | 26 | operations = [ 27 | migrations.RunPython(cache_boughtitemdiscount_values, lambda *a, **k: None), 28 | ] 29 | -------------------------------------------------------------------------------- /brambling/migrations/0007_savedreport.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0006_remove_organization_check_postmark_cutoff'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='SavedReport', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('report_type', models.CharField(max_length=8, choices=[('attendee', 'Attendee'), ('order', 'Order')])), 19 | ('name', models.CharField(max_length=40)), 20 | ('querystring', models.TextField()), 21 | ('event', models.ForeignKey(to='brambling.Event')), 22 | ], 23 | options={ 24 | }, 25 | bases=(models.Model,), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /brambling/payment/stripe/auth.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | 3 | from django.conf import settings 4 | from django.core.urlresolvers import reverse 5 | 6 | from brambling.payment.core import LIVE 7 | from brambling.payment.stripe.core import stripe_prep 8 | 9 | 10 | def stripe_organization_oauth_url(organization, api_type, request): 11 | stripe_prep(api_type) 12 | if api_type == LIVE: 13 | client_id = getattr(settings, 'STRIPE_APPLICATION_ID', None) 14 | else: 15 | client_id = getattr(settings, 'STRIPE_TEST_APPLICATION_ID', None) 16 | if not client_id: 17 | return '' 18 | redirect_uri = request.build_absolute_uri(reverse('brambling_stripe_connect')) 19 | base_url = "https://connect.stripe.com/oauth/authorize?client_id={client_id}&response_type=code&scope=read_write&state={state}&redirect_uri={redirect_uri}" 20 | return base_url.format(client_id=client_id, 21 | state="{}|{}".format(organization.slug, api_type), 22 | redirect_uri=urllib.quote(redirect_uri)) 23 | -------------------------------------------------------------------------------- /brambling/migrations/0013_auto_20150605_1920.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0012_customformfield_help_text'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='dietaryrestriction', 16 | name='name', 17 | field=models.CharField(unique=True, max_length=20), 18 | preserve_default=True, 19 | ), 20 | migrations.AlterField( 21 | model_name='environmentalfactor', 22 | name='name', 23 | field=models.CharField(unique=True, max_length=30), 24 | preserve_default=True, 25 | ), 26 | migrations.AlterField( 27 | model_name='housingcategory', 28 | name='name', 29 | field=models.CharField(unique=True, max_length=20), 30 | preserve_default=True, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /brambling/templates/brambling/user/__base_organize.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/3_9.html' %} 2 | 3 | {% load humanize %} 4 | 5 | {% block side %} 6 | {% url 'brambling_organize_events' as brambling_organize_events_url %} 7 | {% url 'brambling_organize_organizations' as brambling_organize_organizations_url %} 8 |
9 |
10 |

Organize

11 |
12 | 16 | 22 |
23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | addons: 5 | postgresql: "9.4" 6 | sudo: false 7 | env: 8 | global: 9 | - DATABASE_URL=postgres://postgres@localhost:5432/brambling_test 10 | - PIPENV_IGNORE_VIRTUALENVS=1 11 | - PYTHONIOENCODING='utf-8' 12 | cache: 13 | pip: true 14 | directories: 15 | - $HOME/.cache/pipenv 16 | install: 17 | - pip install pipenv 18 | - pipenv install --sequential 19 | - pipenv run pip install flake8==3.3.0 20 | - psql --version 21 | - pipenv --version 22 | - pipenv graph 23 | before_script: 24 | - psql -c 'drop database if exists brambling_test;' -U postgres 25 | - psql -c 'create database brambling_test;' -U postgres 26 | - pipenv run flake8 brambling 27 | script: 28 | - "pipenv run python manage.py test --verbosity=2 brambling" 29 | notifications: 30 | slack: 31 | secure: U8uCS43H0wmkkolDc2VLdwN+vJTxfZtjP/1tHKejUGD6BKWJ5uKBjUjZFpy4O8FcfKkpybSobkK9bgo8/5UV9fi01/Ybli5oBp6gBQARQUKtzFBzLtSGReD5QHa/ezFcoFlxHkCFOZe4oZc/C2+l/x8IyaXxeB3pQvlFeKP+PSw= 32 | on_success: change 33 | on_failure: always 34 | on_start: never 35 | -------------------------------------------------------------------------------- /brambling/migrations/0011_auto_20150603_1708.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0010_auto_20150528_1805'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='customformfield', 16 | name='choices', 17 | field=models.TextField(default='', help_text='Put each choice on its own line', blank=True), 18 | preserve_default=True, 19 | ), 20 | migrations.AlterField( 21 | model_name='customformfield', 22 | name='field_type', 23 | field=models.CharField(default='text', max_length=15, choices=[('text', 'Text'), ('textarea', 'Paragraph text'), ('boolean', 'Checkbox'), ('radio', 'Radio buttons'), ('select', 'Dropdown'), ('checkboxes', 'Multiple checkboxes'), ('select_multiple', 'Dropdown (Multiple)')]), 24 | preserve_default=True, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_mixins.sass: -------------------------------------------------------------------------------- 1 | =absolute-center 2 | position: absolute 3 | top: 0 4 | right: 0 5 | bottom: 0 6 | left: 0 7 | margin: auto 8 | 9 | =full-width 10 | margin-left: -6000px 11 | margin-right: -6000px 12 | padding-left: 6000px 13 | padding-right: 6000px 14 | 15 | =st-ryde-match-x-height 16 | font-size: 1.1em 17 | 18 | =text-light($color) 19 | color: lighten($color, 45%) 20 | .text-accent 21 | color: #FFF 22 | .masthead-link 23 | color: lighten($color, 30%) 24 | &:hover 25 | color: #FFF 26 | 27 | =striped-bg($bg-color: #ccc, $darken-amount: 5%, $stripe-color: darken($bg-color, $darken-amount)) 28 | // http://codepen.io/nathos/pen/ablfk 29 | background-color: $bg-color 30 | background-image: linear-gradient(-45deg, $stripe-color 25%, transparent 25%, transparent 50%, $stripe-color 50%, $stripe-color 75%, transparent 75%, transparent) 31 | background-size: 40px 40px 32 | 33 | =hug 34 | margin-top: -$line-height-computed 35 | margin-bottom: $line-height-computed 36 | border-top-left-radius: 0 37 | border-top-right-radius: 0 38 | -------------------------------------------------------------------------------- /brambling/tests/unit/test_utils_payment.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from unittest import TestCase 3 | 4 | import mock 5 | 6 | from brambling.payment.core import get_fee 7 | 8 | 9 | class GetFeeTestCase(TestCase): 10 | def test_get_fee__decimal(self): 11 | event = mock.MagicMock(application_fee_percent=Decimal('3.33')) 12 | amount = Decimal('10.00') 13 | fee = get_fee(event, amount) 14 | self.assertIsInstance(fee, Decimal) 15 | self.assertEqual(fee, Decimal('0.33')) 16 | 17 | def test_get_fee__int(self): 18 | event = mock.MagicMock(application_fee_percent=Decimal('3.33')) 19 | amount = 10 20 | fee = get_fee(event, amount) 21 | self.assertIsInstance(fee, Decimal) 22 | self.assertEqual(fee, Decimal('0.33')) 23 | 24 | def test_get_fee__float(self): 25 | event = mock.MagicMock(application_fee_percent=Decimal('3.33')) 26 | amount = 10.0 27 | fee = get_fee(event, amount) 28 | self.assertIsInstance(fee, Decimal) 29 | self.assertEqual(fee, Decimal('0.33')) 30 | -------------------------------------------------------------------------------- /brambling/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/12.html' %} 2 | 3 | {% load staticfiles %} 4 | 5 | {% block title %}500 Error – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 |
9 |
10 |

500 Error

11 |

Seems like an error has occurred. But don't worry, here's what you can do about it:

12 | 17 | 18 |

We look forward to hearing from you. Preferably about things that aren't errors. But, you know, we want to hear about the errors, too. ;-)

19 |
20 |
21 | 22 | 23 | {% endblock main %} 24 | -------------------------------------------------------------------------------- /brambling/tests/functional/test_attendee_model.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from brambling.models import Attendee 4 | from brambling.tests.factories import EventFactory, OrderFactory, TransactionFactory, ItemFactory, ItemOptionFactory, AttendeeFactory 5 | 6 | 7 | class AttendeeModelTestCase(TestCase): 8 | 9 | def test_needs_housing(self): 10 | event = EventFactory(collect_housing_data=False) 11 | order = OrderFactory(event=event) 12 | transaction = TransactionFactory(event=event, order=order) 13 | item = ItemFactory(event=event) 14 | item_option = ItemOptionFactory(price=100, item=item) 15 | 16 | order.add_to_cart(item_option) 17 | order.add_to_cart(item_option) 18 | order.mark_cart_paid(transaction) 19 | 20 | a1 = AttendeeFactory(order=order, bought_items=order.bought_items.all(), 21 | housing_status=Attendee.NEED) 22 | a2 = AttendeeFactory(order=order, bought_items=order.bought_items.all()) 23 | self.assertTrue(a1.needs_housing()) 24 | self.assertFalse(a2.needs_housing()) 25 | -------------------------------------------------------------------------------- /brambling/templates/brambling/user/__base_orders.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/3_9.html' %} 2 | 3 | {% load humanize %} 4 | 5 | {% block side %} 6 | {% url 'brambling_order_history' as brambling_order_history_url %} 7 | {% url 'brambling_claim_orders' as brambling_claim_orders_url %} 8 |
9 |
10 |

Orders

11 |
12 | 21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /brambling/auth_backends.py: -------------------------------------------------------------------------------- 1 | class BramblingBackend(object): 2 | """ 3 | Handles object-based permissions for brambling models. 4 | 5 | """ 6 | def authenticate(self, **kwargs): 7 | pass 8 | 9 | def get_user(self, user_id): 10 | pass 11 | 12 | def get_all_permissions(self, user_obj, obj=None): 13 | if obj is None: 14 | return set() 15 | 16 | if not user_obj.is_authenticated() or not user_obj.is_active: 17 | return set() 18 | 19 | if not hasattr(user_obj, '_brambling_perm_cache'): 20 | user_obj._brambling_perm_cache = {} 21 | perm_cache = user_obj._brambling_perm_cache 22 | 23 | cls_name = obj.__class__.__name__ 24 | if cls_name not in perm_cache: 25 | perm_cache[cls_name] = {} 26 | 27 | if obj.pk not in perm_cache[cls_name]: 28 | perm_cache[cls_name][obj.pk] = set(obj.get_permissions(user_obj)) 29 | 30 | return perm_cache[cls_name][obj.pk] 31 | 32 | def has_perm(self, user_obj, perm, obj=None): 33 | return perm in self.get_all_permissions(user_obj, obj) 34 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/_when.html: -------------------------------------------------------------------------------- 1 | {% load zenaida tz %} 2 | 3 | {% timezone event.timezone %} 4 | {% td_to_dict event.start_date|td_timeuntil as time_until_event %} 5 | {% endtimezone %} 6 | 7 | {# start date #} 8 | {% if time_until_event.days == 0 %} 9 | Today 10 | {% elif time_until_event.days == 1 %} 11 | Tomorrow 12 | {% else %} 13 | {{ event.start_date }} 14 | {% endif %} 15 | 16 | {% if event.start_date == event.end_date %} 17 | {% if event.start_time and event.end_time %} 18 | from {{ event.start_time }} to {{ event.end_time }} 19 | {% elif event.start_time %} 20 | at {{ event.start_time }} 21 | {% elif event.end_time %} 22 | until {{ event.end_time }} 23 | {% endif %} 24 | {% else %} 25 | {% if event.start_time %} 26 | at {{ event.start_time }} 27 | {% endif %} 28 | 29 | {# end date #} 30 | to {{ event.end_date }} 31 | 32 | {% if event.end_time %} 33 | at {{ event.end_time }} 34 | {% endif %} 35 | {% endif %} 36 | 37 | {# days left#} 38 | {% if time_until_event.days > 1 and time_until_event.days < 15 %} 39 | (in {{ time_until_event.days }} day{{ time_until_event|pluralize }}) 40 | {% endif %} 41 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/order_alert/body_plaintext.txt: -------------------------------------------------------------------------------- 1 | {% autoescape off %}You got a new purchase! Congrats! Here's the summary. 2 | 3 | {{ event.name }} by {{ event.organization.name }} 4 | Order #{{ order.code }} 5 | {{ transaction.timestamp }} 6 | 7 | Paid{% load zenaida %} 8 | {{ transaction.amount|format_money:event.currency }}{% if unconfirmed_check_payments %} 9 | 10 | This was a check payment, so keep an eye on your mail!{% endif %} 11 | 12 | Attendees{% regroup bought_items by attendee as item_list %}{% for item in item_list %} 13 | {{ item.grouper.get_full_name }}{% endfor %} 14 | 15 | Items{% for item in bought_items %} 16 | {{ item.item_name }} ({{ item.item_option_name }}){% endfor %} 17 | {% url 'brambling_event_order_detail' event_slug=event.slug organization_slug=event.organization.slug code=order.code as url %} 18 | View more details at the following URL: 19 | {{ protocol }}://{{ site.domain }}{{ url }} 20 | 21 | Update your notification settings here: {{ protocol }}://{{ site.domain }}{% url "brambling_user_notifications" %} 22 | {% endautoescape %} 23 | 24 | Dancerfly (dancerfly.com) is a project of Harris Lapiroff 25 | -------------------------------------------------------------------------------- /brambling/static/brambling/fonts/stryde.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * MyFonts Webfont Build ID 2833283, 2014-06-23T07:14:56-0400 4 | * 5 | * The fonts listed in this notice are subject to the End User License 6 | * Agreement(s) entered into by the website owner. All other parties are 7 | * explicitly restricted from using the Licensed Webfonts(s). 8 | * 9 | * You may obtain a valid license at the URLs below. 10 | * 11 | * Webfont: St Ryde Regular by Stereotypes 12 | * URL: http://www.myfonts.com/fonts/stereotypes/st-ryde/regular/ 13 | * Copyright: Copyright (c) 2010 by Stereotypes.de. All rights reserved. 14 | * Licensed pageviews: 10,000 15 | * 16 | * 17 | * License: http://www.myfonts.com/viewlicense?type=web&buildid=2833283 18 | * 19 | * © 2014 MyFonts Inc 20 | */ 21 | 22 | 23 | /* @import must be at top of file, otherwise CSS will not work */ 24 | @import url("//hello.myfonts.net/count/2b3b83"); 25 | 26 | 27 | @font-face {font-family: 'St Ryde';src: url('stryde/2B3B83_0_0.eot');src: url('stryde/2B3B83_0_0.eot?#iefix') format('embedded-opentype'),url('stryde/2B3B83_0_0.woff') format('woff'),url('stryde/2B3B83_0_0.ttf') format('truetype');} 28 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/styles.sass: -------------------------------------------------------------------------------- 1 | // Dancerfly 2 | @import variables 3 | @import "../../brambling/fonts/stryde.css" 4 | @import "../../brambling/fonts/stryde-bold.css" 5 | 6 | // The following imports are from https://github.com/twbs/bootstrap-sass/blob/master/vendor/assets/stylesheets/bootstrap.scss 7 | // with glyphicons removed. 8 | 9 | // Bootstrap 10 | @import bootstrap/bootstrap 11 | 12 | // Zenaida 13 | @import chosen 14 | 15 | // Brambling 16 | @import modules/mixins 17 | @import modules/base 18 | @import modules/text 19 | @import modules/layout 20 | // 21 | @import modules/carousel 22 | @import modules/colors 23 | @import modules/event-listing 24 | @import modules/footer 25 | @import modules/forms 26 | @import modules/headings 27 | @import modules/images 28 | @import modules/list-item 29 | @import modules/list-group 30 | @import modules/marketing 31 | @import modules/masthead 32 | @import modules/menus 33 | @import modules/modals 34 | @import modules/panels 35 | @import modules/popovers 36 | @import modules/scrim 37 | @import modules/shortened 38 | @import modules/stepbar 39 | 40 | // Dancerfly 41 | @import modules/navbar 42 | @import modules/dancerfly 43 | -------------------------------------------------------------------------------- /brambling/migrations/0020_auto_20150609_0033.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0019_auto_20150528_1850'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='event', 16 | name='privacy', 17 | field=models.CharField(default='public', max_length=11, choices=[('public', 'Anyone can find and view the event'), ('link', 'Anyone with a direct link can view the event'), ('half-public', 'Anyone can find and view the event, but only people who are invited can register'), ('invited', 'Only people invited to the event can see the event and register')]), 18 | preserve_default=True, 19 | ), 20 | migrations.AlterField( 21 | model_name='invite', 22 | name='kind', 23 | field=models.CharField(max_length=10, choices=[('event', 'Event'), ('editor', 'Event Editor'), ('org_editor', 'Organization Editor')]), 24 | preserve_default=True, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /brambling/migrations/0044_auto_20160112_2020.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from decimal import Decimal 6 | import django.core.validators 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('brambling', '0043_auto_20160105_2012'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='event', 18 | name='application_fee_percent', 19 | field=models.DecimalField(default=Decimal('2.5'), max_digits=5, decimal_places=2, validators=[django.core.validators.MaxValueValidator(100), django.core.validators.MinValueValidator(0)]), 20 | preserve_default=True, 21 | ), 22 | migrations.AlterField( 23 | model_name='organization', 24 | name='default_application_fee_percent', 25 | field=models.DecimalField(default=Decimal('2.5'), max_digits=5, decimal_places=2, validators=[django.core.validators.MaxValueValidator(100), django.core.validators.MinValueValidator(0)]), 26 | preserve_default=True, 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /brambling/static/brambling/fonts/stryde-bold.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * MyFonts Webfont Build ID 2866029, 2014-08-19T22:47:41-0400 4 | * 5 | * The fonts listed in this notice are subject to the End User License 6 | * Agreement(s) entered into by the website owner. All other parties are 7 | * explicitly restricted from using the Licensed Webfonts(s). 8 | * 9 | * You may obtain a valid license at the URLs below. 10 | * 11 | * Webfont: St Ryde Bold by Stereotypes 12 | * URL: http://www.myfonts.com/fonts/stereotypes/st-ryde/bold/ 13 | * Copyright: Copyright (c) 2010 by Stereotypes.de. All rights reserved. 14 | * Licensed pageviews: 10,000 15 | * 16 | * 17 | * License: http://www.myfonts.com/viewlicense?type=web&buildid=2866029 18 | * 19 | * © 2014 MyFonts Inc 20 | */ 21 | 22 | 23 | /* @import must be at top of file, otherwise CSS will not work */ 24 | @import url("//hello.myfonts.net/count/2bbb6d"); 25 | 26 | 27 | @font-face {font-family: 'St Ryde'; font-weight: bold;src: url('stryde-bold/2BBB6D_0_0.eot');src: url('stryde-bold/2BBB6D_0_0.eot?#iefix') format('embedded-opentype'),url('stryde-bold/2BBB6D_0_0.woff') format('woff'),url('stryde-bold/2BBB6D_0_0.ttf') format('truetype');} 28 | -------------------------------------------------------------------------------- /brambling/migrations/0034_auto_20151104_0259.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0033_attendee_notes'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='organization', 16 | name='default_event_city', 17 | ), 18 | migrations.RemoveField( 19 | model_name='organization', 20 | name='default_event_country', 21 | ), 22 | migrations.RemoveField( 23 | model_name='organization', 24 | name='default_event_currency', 25 | ), 26 | migrations.RemoveField( 27 | model_name='organization', 28 | name='default_event_dance_styles', 29 | ), 30 | migrations.RemoveField( 31 | model_name='organization', 32 | name='default_event_state_or_province', 33 | ), 34 | migrations.RemoveField( 35 | model_name='organization', 36 | name='default_event_timezone', 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /brambling/templates/brambling/forms/signup.html: -------------------------------------------------------------------------------- 1 | {% load floppyforms %} 2 | 3 | {% formconfig row using "brambling/forms/signup_row.html" %} 4 | 5 |
6 |
7 | {% formrow form.first_name %} 8 |
9 |
10 | {% formrow form.last_name %} 11 |
12 |
13 | 14 |
15 |
16 | {% formrow form.middle_name %} 17 |
18 |
19 | {% formrow form.name_order %} 20 |
21 |
22 | 23 |
24 |
25 | More Name Options 26 | {% formrow form.email %} 27 |
28 |
29 | 30 |
31 |
32 | {% formrow form.password1 %} 33 |
34 |
35 | {% formrow form.password2 %} 36 |
37 |
38 | 39 | {% for field in form.hidden_fields %} 40 | {{ field }} 41 | {% endfor %} 42 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_forms.sass: -------------------------------------------------------------------------------- 1 | .form-control:focus 2 | box-shadow: 0 0 0 1px $yellow-light inset 3 | 4 | .chosen-container-active 5 | &.chosen-container-multi .chosen-choices, &.chosen-container-single .chosen-single 6 | box-shadow: 0 0 0 1px $yellow-light inset 7 | 8 | .chosen-container-single .chosen-drop 9 | border: 2px solid $yellow-light 10 | border-top: none 11 | 12 | .chosen-container.chosen-width-auto .chosen-drop 13 | width: auto 14 | border: 2px solid $yellow-light 15 | top: 110% 16 | 17 | .chosen-results li 18 | white-space: nowrap 19 | 20 | .form-group.has-error 21 | .chosen-container-multi .chosen-choices, 22 | .chosen-container-single .chosen-single 23 | border-color: $state-danger-text 24 | 25 | .more-name-options 26 | position: relative 27 | top: -13px 28 | 29 | .label-block 30 | font-weight: inherit 31 | display: block 32 | 33 | // Our our form variations: 34 | .form--single-row 35 | .form-group 36 | margin-bottom: 0 37 | 38 | .input-group-addon-overlay-right 39 | position: absolute 40 | background-color: transparent 41 | z-index: 3 42 | right: 0 43 | top: 50% 44 | transform: translate(0, -50%) 45 | margin-right: 10px 46 | color: #666 47 | -------------------------------------------------------------------------------- /brambling/migrations/0031_auto_20151010_0621.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0030_auto_20151009_0631'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='person', 16 | name='ef_avoid', 17 | ), 18 | migrations.RemoveField( 19 | model_name='person', 20 | name='ef_cause', 21 | ), 22 | migrations.RemoveField( 23 | model_name='person', 24 | name='housing_prefer', 25 | ), 26 | migrations.RemoveField( 27 | model_name='person', 28 | name='other_needs', 29 | ), 30 | migrations.RemoveField( 31 | model_name='person', 32 | name='person_avoid', 33 | ), 34 | migrations.RemoveField( 35 | model_name='person', 36 | name='person_prefer', 37 | ), 38 | migrations.RemoveField( 39 | model_name='person', 40 | name='phone', 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /brambling/templates/brambling/user/notifications.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/user/__base.html' %} 2 | 3 | {% load floppyforms static %} 4 | 5 | {% block title %}Notification settings – {{ block.super }}{% endblock %} 6 | 7 | 8 | {% block main %} 9 | {{ block.super }} 10 |

Notification settings

11 | 12 | {% include "brambling/forms/_top_error_list.html" %} 13 | 14 |
15 | {% csrf_token %} 16 |
17 |
18 |

Notifications

19 |
20 |
21 | {% form form using %} 22 | {% formconfig row using "brambling/forms/rows/bootstrap.html" %} 23 | {% formrow form.notify_new_purchases with label="Notification frequency for events you own or manage" %} 24 | {% formrow form.notify_product_updates with label="Email me occasionally about Dancerfly updates that are relevant to my events." %} 25 | {% endform %} 26 |
27 |
28 | 29 | Cancel 30 |
31 | 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /brambling/templatetags/brambling_markdown.py: -------------------------------------------------------------------------------- 1 | # encoding: utf8 2 | from __future__ import absolute_import, division, unicode_literals 3 | 4 | import bleach 5 | from bleach.callbacks import nofollow, target_blank 6 | from django import template 7 | from django.utils.safestring import mark_safe 8 | import markdown 9 | from markdown.extensions.smarty import SmartyExtension 10 | 11 | 12 | register = template.Library() 13 | 14 | 15 | class SkipImagesExtension(markdown.extensions.Extension): 16 | def extendMarkdown(self, md, md_globals): 17 | del md.inlinePatterns['image_link'] 18 | del md.inlinePatterns['image_reference'] 19 | 20 | 21 | md = markdown.Markdown(extensions=['markdown.extensions.nl2br', 22 | 'markdown.extensions.sane_lists', 23 | SmartyExtension(smart_angled_quotes=True), 24 | SkipImagesExtension()], 25 | output_format='html5') 26 | 27 | 28 | @register.filter(name='markdown') 29 | def markdown_filter(text): 30 | text = bleach.clean(text, strip=True) 31 | text = md.reset().convert(text) 32 | return mark_safe(bleach.linkify(text, [nofollow, target_blank])) 33 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_marketing.sass: -------------------------------------------------------------------------------- 1 | body.marketing-page 2 | padding-top: 40px 3 | 4 | .marketing 5 | font-size: $font-size-base * 1.25 6 | padding: $line-height-computed 0 7 | 8 | .btn-xl 9 | font-size: 1.5 * $font-size-base 10 | padding: $font-size-base 2*$font-size-base 11 | 12 | @media (max-width: $screen-xs-max) 13 | .col-sm-4 14 | text-align: center 15 | 16 | @media (min-width: $screen-sm-min) 17 | padding: 2*$line-height-computed 2*$line-height-computed 18 | 19 | @media (min-width: $screen-md-min) 20 | padding: 3*$line-height-computed 3*$line-height-computed 21 | .col-sm-4 22 | padding: 0 2*$line-height-computed 23 | 24 | .marketing-grey 25 | background: #eee 26 | 27 | .marketing-brand 28 | background: $yellow-darker 29 | color: #fff 30 | 31 | .marketing-brand-link 32 | color: lighten($yellow-light, 30%) 33 | 34 | .marketing-brand-btn, .marketing-brand-btn:hover 35 | border-color: darken($yellow-darker,10%) 36 | 37 | .marketing-icon-header .fa 38 | vertical-align: middle 39 | 40 | .marketing-dancers-img 41 | @media(min-width: $screen-sm-min) 42 | margin: -20px auto 43 | -------------------------------------------------------------------------------- /brambling/migrations/0045_auto_20160104_2208.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0044_auto_20160112_2020'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='person', 16 | name='last_new_purchases_digest_sent', 17 | field=models.DateTimeField(default=None, null=True, blank=True), 18 | preserve_default=True, 19 | ), 20 | migrations.AddField( 21 | model_name='person', 22 | name='notify_new_purchases', 23 | field=models.CharField(default='each', max_length=5, choices=[('never', "Don't email me about new purchases"), ('each', 'Email me about every new purchase'), ('daily', 'Email me a daily report of new purchases')]), 24 | preserve_default=True, 25 | ), 26 | migrations.AddField( 27 | model_name='person', 28 | name='notify_product_updates', 29 | field=models.BooleanField(default=True), 30 | preserve_default=True, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/_nav.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {% include 'brambling/event/organizer/_nav_item.html' with event=event url_name='brambling_event_summary' name='Summary' icon='dashboard' request=request only %} 4 |
5 |
6 | 7 | {% for group in event_admin_nav %} 8 |
9 | 17 | 24 |
25 | {% endfor %} 26 | -------------------------------------------------------------------------------- /brambling/templates/brambling/organization/_nav.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 | {{ organization.name }} 6 | 7 |

8 | {% if organization.city or organization.state_or_province or organization.country %} 9 |
10 |
11 | 12 |
13 |
14 | {% include "brambling/organization/_where.html" %} 15 |
16 |
17 | {% endif %} 18 |
19 |
20 | {% for item in organization_admin_nav %} 21 | 22 | {% if item.icon %} 23 | 24 | {% endif %} 25 | {{ item.label }} 26 | 27 | {% endfor %} 28 |
29 | 35 |
36 | -------------------------------------------------------------------------------- /brambling/migrations/0056_auto_20170606_1844.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0055_auto_20170601_2144'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='ProcessedStripeEvent', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('api_type', models.CharField(default=b'live', max_length=4, choices=[(b'live', 'Live'), (b'test', 'Test')])), 19 | ('stripe_event_id', models.CharField(max_length=255)), 20 | ('created', models.DateTimeField(auto_now_add=True)), 21 | ], 22 | ), 23 | migrations.DeleteModel( 24 | name='ProcessedStripeLiveEvent', 25 | ), 26 | migrations.DeleteModel( 27 | name='ProcessedStripeTestEvent', 28 | ), 29 | migrations.AlterUniqueTogether( 30 | name='processedstripeevent', 31 | unique_together=set([('api_type', 'stripe_event_id')]), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/refund_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/event/organizer/__narrow.html' %} 2 | 3 | {% load static %} 4 | 5 | {% block title %}Refund Transaction from Order {{ order.code }} – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 | {{ block.super }} 9 | 10 |

Refund Transaction from Order {{ order.code }}

11 | 12 |
13 | {% csrf_token %} 14 |
15 | {% include "brambling/event/organizer/_refund_form_inner.html" with txn=transaction %} 16 |
17 | 18 | Cancel 19 | 20 | {% endblock %} 21 | 22 | {% block javascripts %} 23 | {{ block.super }} 24 | 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /brambling/migrations/0058_auto_20190114_0455.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2019-01-14 04:55 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('brambling', '0057_auto_20190105_0626'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='order', 17 | name='dwolla_account', 18 | ), 19 | migrations.RemoveField( 20 | model_name='order', 21 | name='dwolla_test_account', 22 | ), 23 | migrations.RemoveField( 24 | model_name='organization', 25 | name='dwolla_account', 26 | ), 27 | migrations.RemoveField( 28 | model_name='organization', 29 | name='dwolla_test_account', 30 | ), 31 | migrations.RemoveField( 32 | model_name='person', 33 | name='dwolla_account', 34 | ), 35 | migrations.RemoveField( 36 | model_name='person', 37 | name='dwolla_test_account', 38 | ), 39 | migrations.DeleteModel( 40 | name='DwollaAccount', 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /brambling/migrations/0055_auto_20170601_2144.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0054_processedstripeevent'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='ProcessedStripeLiveEvent', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('stripe_event_id', models.CharField(unique=True, max_length=255)), 19 | ('created', models.DateTimeField(auto_now_add=True)), 20 | ], 21 | ), 22 | migrations.CreateModel( 23 | name='ProcessedStripeTestEvent', 24 | fields=[ 25 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 26 | ('stripe_event_id', models.CharField(unique=True, max_length=255)), 27 | ('created', models.DateTimeField(auto_now_add=True)), 28 | ], 29 | ), 30 | migrations.DeleteModel( 31 | name='ProcessedStripeEvent', 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /brambling/tests/unit/test_utils_international.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from django.core.exceptions import ValidationError 4 | 5 | from brambling.utils.international import clean_postal_code 6 | 7 | 8 | class PostalCodeTestCase(TestCase): 9 | def test_gb_postal__valid1(self): 10 | clean_postal_code('GB', 'NW4 9BX') 11 | 12 | def test_gb_postal__valid2(self): 13 | clean_postal_code('GB', 'M32 8BR') 14 | 15 | def test_gb_postal__valid3(self): 16 | clean_postal_code('GB', 'M3 3BT') 17 | 18 | def test_gb_postal__invalid1(self): 19 | with self.assertRaises(ValidationError): 20 | clean_postal_code('GB', '12345') 21 | 22 | def test_gb_postal__empty(self): 23 | clean_postal_code('US', '') 24 | 25 | def test_us_postal__valid1(self): 26 | clean_postal_code('US', '16801') 27 | 28 | def test_us_postal__valid2(self): 29 | clean_postal_code('US', '16801-2345') 30 | 31 | def test_us_postal__invalid1(self): 32 | with self.assertRaises(ValidationError): 33 | clean_postal_code('US', 'ABCDEF') 34 | 35 | def test_us_postal__empty(self): 36 | clean_postal_code('US', '') 37 | 38 | def test_unhandled_country(self): 39 | clean_postal_code('12', ';[]32adf,,,') 40 | -------------------------------------------------------------------------------- /brambling/tests/unit/test_model_tables.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from brambling.utils.model_tables import Row 4 | 5 | 6 | class RowTestCase(TestCase): 7 | def test_iterable(self): 8 | data = [ 9 | ('id', 5), 10 | ('name', 'Phil'), 11 | ] 12 | row = Row(data) 13 | result = [(cell.field, cell.value) for cell in row] 14 | self.assertEqual(result, data) 15 | 16 | def test_getitem__string(self): 17 | data = [ 18 | ('id', 5), 19 | ('name', 'Phil'), 20 | ] 21 | row = Row(data) 22 | id_cell = row['id'] 23 | name_cell = row['name'] 24 | self.assertEqual(id_cell.field, 'id') 25 | self.assertEqual(id_cell.value, 5) 26 | self.assertEqual(name_cell.field, 'name') 27 | self.assertEqual(name_cell.value, 'Phil') 28 | 29 | def test_getitem__int(self): 30 | data = [ 31 | ('id', 5), 32 | ('name', 'Phil'), 33 | ] 34 | row = Row(data) 35 | id_cell = row[0] 36 | name_cell = row[1] 37 | self.assertEqual(id_cell.field, 'id') 38 | self.assertEqual(id_cell.value, 5) 39 | self.assertEqual(name_cell.field, 'name') 40 | self.assertEqual(name_cell.value, 'Phil') 41 | -------------------------------------------------------------------------------- /brambling/api/v1/endpoints/item.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers, viewsets 2 | 3 | from brambling.api.v1.permissions import BaseEventPermission 4 | from brambling.api.v1.endpoints.itemoption import ItemOptionSerializer 5 | from brambling.api.v1.endpoints.itemimage import ItemImageSerializer 6 | from brambling.models import Item 7 | 8 | 9 | class ItemPermission(BaseEventPermission): 10 | def has_object_permission(self, request, view, item): 11 | return self._has_event_permission(request, item.event) 12 | 13 | 14 | class ItemSerializer(serializers.HyperlinkedModelSerializer): 15 | link = serializers.HyperlinkedIdentityField(view_name='item-detail') 16 | options = ItemOptionSerializer(many=True) 17 | images = ItemImageSerializer(many=True) 18 | 19 | class Meta: 20 | model = Item 21 | fields = ('id', 'link', 'name', 'description', 'event', 'created', 22 | 'last_modified', 'options', 'images') 23 | 24 | 25 | class ItemViewSet(viewsets.ModelViewSet): 26 | queryset = Item.objects.all() 27 | serializer_class = ItemSerializer 28 | permission_classes = [ItemPermission] 29 | 30 | def get_queryset(self): 31 | qs = Item.objects.all() 32 | 33 | if 'event' in self.request.GET: 34 | qs = qs.filter(event=self.request.GET['event']) 35 | 36 | return qs 37 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/order/_check_payment_info.html: -------------------------------------------------------------------------------- 1 | {% load zenaida %}{% td_to_dict event.check_postmark_cutoff|td_timeuntil as time_until_check_cutoff %} 2 | {% if time_until_check_cutoff.days >= 0 %} 3 |

Checks should be made out to {{ event.organization.check_payable_to }} and include your order code ({{ order.code }}) in the memo line. Mail them to:

4 |

5 | {{ event.organization.check_recipient }}
6 | {{ event.organization.check_address }}
{% if event.organization.check_address_2 %} 7 | {{ event.organization.check_address_2 }}
{% endif %} 8 | {{ event.organization.check_city }}, {{ event.organization.check_state_or_province }} {{ event.organization.check_zip }} {{ event.organization.check_country }} 9 |

10 |

Checks must be postmarked no later than {% if time_until_check_cutoff.days == 0%}today{% else %}{{ event.check_postmark_cutoff|date:"F jS, Y" }}{% endif %}. If your payment is not postmarked by {% if time_until_check_cutoff.days == 0%}today{% else %}that date{% endif %}, the event organizer reserves the right to revoke your passes.

11 | {% else %} 12 |

Payment was due to be postmarked no later than {{ event.check_postmark_cutoff|date:"F jS, Y" }}. The event organizer reserves the right to revoke your passes if you have not sent in your payment.

13 | {% endif %} 14 | -------------------------------------------------------------------------------- /brambling/migrations/0042_auto_20160105_2012.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0041_auto_20160104_2321'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='attendee', 16 | name='name_order', 17 | field=models.CharField(default='FML', max_length=3, choices=[('FML', 'First Middle Last'), ('LFM', 'Last First Middle'), ('FL', 'First Last'), ('LF', 'Last First')]), 18 | preserve_default=True, 19 | ), 20 | migrations.AlterField( 21 | model_name='person', 22 | name='name_order', 23 | field=models.CharField(default='FML', max_length=3, choices=[('FML', 'First Middle Last'), ('LFM', 'Last First Middle'), ('FL', 'First Last'), ('LF', 'Last First')]), 24 | preserve_default=True, 25 | ), 26 | migrations.AlterField( 27 | model_name='savedattendee', 28 | name='name_order', 29 | field=models.CharField(default='FML', max_length=3, choices=[('FML', 'First Middle Last'), ('LFM', 'Last First Middle'), ('FL', 'First Last'), ('LF', 'Last First')]), 30 | preserve_default=True, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /brambling/static/brambling/brambling.utils.js: -------------------------------------------------------------------------------- 1 | // Some simple Javascript utilities to be used wherever: 2 | 3 | ;(function () { 4 | "use strict"; 5 | 6 | // Filters to be used in Ractive mustaches and elsewhere: 7 | brambling.filters = { 8 | // Wraps a bit of text in an element: 9 | highlight: function (needle, haystack, open_tag, close_tag) { 10 | var open = open_tag || "", 11 | close = close_tag || "", 12 | pos = 0, 13 | returnVal = haystack, 14 | needle = needle.toLowerCase(); 15 | 16 | if (!haystack) return; 17 | if (!needle) return returnVal; 18 | 19 | // Find all occurrences: 20 | while (pos !== -1) { 21 | pos = haystack.toLowerCase().indexOf(needle, pos); 22 | if (pos === -1) break; 23 | returnVal = returnVal.substring(0, pos) + open + returnVal.substring(pos, pos + needle.length) + close + returnVal.substring(pos + needle.length); 24 | pos = pos + needle.length + open.length + close.length; 25 | } 26 | 27 | return returnVal; 28 | }, 29 | formatMoney: function (amount, currency) { 30 | var symbol_map = { 31 | USD: '$', 32 | GBP: '£' 33 | }; 34 | var formattedAmount = amount.toFixed(2); 35 | 36 | if (symbol_map[currency]) { 37 | return symbol_map[currency] + formattedAmount; 38 | } 39 | return '' + formattedAmount + ' ' + currency; 40 | } 41 | }; 42 | 43 | }()); 44 | -------------------------------------------------------------------------------- /brambling/templates/brambling/user/organizations.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/user/__base_organize.html' %} 2 | 3 | {% load floppyforms humanize zenaida %} 4 | 5 | {% block title %}Organizations – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 | {{ block.super }} 9 |

Organizations

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for organization in organizations %} 21 | 22 | 27 | 28 | 34 | 35 | {% empty %} 36 | 37 | 40 | 41 | {% endfor %} 42 | 43 |
OrganizationLast activity (UTC)
23 | 24 | {{ organization.name }} 25 | 26 | {{ organization.last_modified }} 29 | 30 | 31 | Settings 32 | 33 |
38 | It doesn't look like you have any events right now; go get started! 39 |
44 | 45 | {% endblock %} 46 | -------------------------------------------------------------------------------- /brambling/tests/functional/test_processed_stripe_event_model.py: -------------------------------------------------------------------------------- 1 | from django.db import IntegrityError 2 | from django.test import TestCase 3 | 4 | from brambling.models import ProcessedStripeEvent 5 | from brambling.tests.factories import ProcessedStripeEventFactory 6 | 7 | 8 | class ProcessedStripeEventTestCase(TestCase): 9 | def test_should_allow_events_with_same_id_if_different_api_type(self): 10 | event_id = '1' 11 | 12 | ProcessedStripeEventFactory( 13 | stripe_event_id=event_id, 14 | api_type=ProcessedStripeEvent.LIVE, 15 | ) 16 | 17 | ProcessedStripeEvent.objects.create( 18 | stripe_event_id=event_id, 19 | api_type=ProcessedStripeEvent.TEST, 20 | ) 21 | 22 | def test_api_type_and_event_id_should_be_unique_together(self): 23 | event_id = '1' 24 | 25 | ProcessedStripeEventFactory( 26 | stripe_event_id=event_id, 27 | api_type=ProcessedStripeEvent.LIVE, 28 | ) 29 | 30 | with self.assertRaises(IntegrityError): 31 | ProcessedStripeEvent.objects.create( 32 | stripe_event_id=event_id, 33 | api_type=ProcessedStripeEvent.LIVE, 34 | ) 35 | 36 | def test_barf(self): 37 | ProcessedStripeEvent.objects.create( 38 | stripe_event_id='qq', 39 | api_type='barf', 40 | ) 41 | -------------------------------------------------------------------------------- /brambling/templates/brambling/global.html: -------------------------------------------------------------------------------- 1 | {% extends "brambling/layouts/6_6.html" %} 2 | 3 | {% load staticfiles %} 4 | 5 | {% block title %}International support – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 |

International Support

9 |
10 |

Dancerfly works anywhere! ... mostly.

11 |
12 | 13 |

Language

14 | 15 |

Dancerfly is only available in English right now. We might expand into other languages someday; if you want to be kept informed, or just want to let us know you'd totally use us if we supported your language, email us at support@dancerfly.com.

16 | 17 |

Payment processors

18 | 19 |

Stripe, our credit card processor, is currently available in 19 countries.

20 | 21 |

If that option doesn't work for you, you can still accept payments by check. If that doesn't work for you either, and this is the only thing keeping you from using our platform, please email us at support@dancerfly.com, and we'll see if anything can be done.

22 | {% endblock %} 23 | 24 | {% block side %} 25 | 26 | {% endblock side %} 27 | -------------------------------------------------------------------------------- /brambling/static/brambling/lib/ractive-transitions-slide.js: -------------------------------------------------------------------------------- 1 | // https://raw.githubusercontent.com/ractivejs/ractive-transitions-slide/master/dist/ractive-transitions-slide.js 2 | (function (global, factory) { 3 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 4 | typeof define === 'function' && define.amd ? define(factory) : 5 | global.Ractive.transitions.slide = factory() 6 | }(this, function () { 'use strict'; 7 | 8 | var ractive_transitions_slide = slide; 9 | var DEFAULTS = { 10 | duration: 300, 11 | easing: "easeInOut" 12 | }; 13 | 14 | var PROPS = ["height", "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom", "marginTop", "marginBottom"]; 15 | 16 | var COLLAPSED = { 17 | height: 0, 18 | borderTopWidth: 0, 19 | borderBottomWidth: 0, 20 | paddingTop: 0, 21 | paddingBottom: 0, 22 | marginTop: 0, 23 | marginBottom: 0 24 | }; 25 | function slide(t, params) { 26 | var targetStyle; 27 | 28 | params = t.processParams(params, DEFAULTS); 29 | 30 | if (t.isIntro) { 31 | targetStyle = t.getStyle(PROPS); 32 | t.setStyle(COLLAPSED); 33 | } else { 34 | // make style explicit, so we're not transitioning to 'auto' 35 | t.setStyle(t.getStyle(PROPS)); 36 | targetStyle = COLLAPSED; 37 | } 38 | 39 | t.setStyle("overflowY", "hidden"); 40 | 41 | t.animateStyle(targetStyle, params).then(t.complete); 42 | } 43 | 44 | return ractive_transitions_slide; 45 | 46 | })); 47 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/order_receipt/body_plaintext.txt: -------------------------------------------------------------------------------- 1 | {% autoescape off %}{{ event.name }}{% load zenaida %} 2 | {{ event.organization.name }} 3 | {{ event.start_date|date:"F jS, Y" }} 4 | 5 | {{ transaction.amount|format_money:event.currency }} 6 | 7 | Happy dancing! :-) 8 | 9 | {% if unconfirmed_check_payments %} 10 | 11 | Note: Your passes are reserved, but the organizer has not yet received your payment by mail. 12 | {% filter striptags %}{% include "brambling/event/order/_check_payment_info.html" %}{% endfilter %} 13 | If you believe this is in error, please contact the event organizer directly. 14 | {% endif %} 15 | 16 | {% for item in transaction.bought_items.all %}{% if item.status == item.BOUGHT %}{{ item.item_name }} ({{ item.item_option_name }}) 17 | {{ item.attendee.get_full_name }} 18 | {{ item.price|format_money:event.currency }} 19 | {% for discount in item.discounts.all %}({{ discount.savings|format_money:event.currency }}) 20 | {% endfor %} 21 | {% endif %}{% endfor %}---------------------- 22 | 23 | Total: {{ transaction.amount|format_money:event.currency }} 24 | 25 | Order #{{ order.code }} 26 | {{ transaction.timestamp }} 27 | 28 | {% url 'brambling_order_code_redirect' event_slug=event.slug organization_slug=event.organization.slug code=order.code as url %} 29 | View and edit your order at the following URL: 30 | {{ protocol }}://{{ site.domain }}{{ url }}{% endautoescape %} 31 | 32 | 33 | Dancerfly (dancerfly.com) is a project of Harris Lapiroff 34 | -------------------------------------------------------------------------------- /brambling/migrations/0050_auto_20160228_0059.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('brambling', '0049_auto_20160213_0401'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='eventhousing', 16 | name='contact_email', 17 | field=models.EmailField(max_length=254, blank=True), 18 | ), 19 | migrations.AlterField( 20 | model_name='invite', 21 | name='email', 22 | field=models.EmailField(max_length=254), 23 | ), 24 | migrations.AlterField( 25 | model_name='order', 26 | name='email', 27 | field=models.EmailField(max_length=254, blank=True), 28 | ), 29 | migrations.AlterField( 30 | model_name='person', 31 | name='groups', 32 | field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups'), 33 | ), 34 | migrations.AlterField( 35 | model_name='person', 36 | name='last_login', 37 | field=models.DateTimeField(null=True, verbose_name='last login', blank=True), 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /brambling/management/commands/send_daily_emails.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import traceback 3 | 4 | from django.core.management.base import BaseCommand 5 | from django.utils import timezone 6 | 7 | from brambling.mail import DailyDigestMailer 8 | from brambling.models import Person 9 | 10 | 11 | class Command(BaseCommand): 12 | def get_recipients(self): 13 | return Person.objects.filter( 14 | notify_new_purchases=Person.NOTIFY_DAILY, 15 | ) 16 | 17 | def send_digest(self, recipient): 18 | cutoff = ( 19 | recipient.last_new_purchases_digest_sent or 20 | (timezone.now() - datetime.timedelta(days=1)) 21 | ) 22 | mailer = DailyDigestMailer( 23 | recipient=recipient, 24 | cutoff=cutoff, 25 | site={'domain': 'dancerfly.com'}, 26 | secure=True, 27 | ) 28 | mailer.send() 29 | 30 | def handle(self, *args, **options): 31 | recipients = self.get_recipients() 32 | for recipient in recipients: 33 | try: 34 | self.send_digest(recipient) 35 | except Exception: 36 | self.stderr.write("Digest send raised an error for {recipient} ({pk})".format( 37 | recipient=recipient.get_full_name(), 38 | pk=recipient.pk 39 | )) 40 | self.stderr.write(traceback.format_exc()) 41 | recipients.update(last_new_purchases_digest_sent=timezone.now()) 42 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/discount_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/event/organizer/__base.html' %} 2 | 3 | {% load zenaida %} 4 | 5 | {% block title %}Discounts – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 | {{ block.super }} 9 |
10 | {% include "brambling/event/_empty_shop_alert.html" %} 11 | {% if discounts %} 12 |
13 |
14 |
15 |

Discounts

16 |
17 |
18 | {% for discount in discounts %} 19 | {{ discount.name }} {{ discount.code }} 20 | {% endfor %} 21 |
22 |
23 |
24 | {% endif %} 25 | {% if 'edit' in event_permissions %} 26 |
27 |
28 |
29 |

Create Discount

30 |
31 |
32 | {% url 'brambling_discount_create' event_slug=event.slug organization_slug=event.organization.slug as action %} 33 | {% include "brambling/event/organizer/_discount_form.html" with action=action %} 34 |
35 |
36 |
37 | {% endif %} 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Harris Lapiroff 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /brambling/migrations/0035_auto_20151202_1727.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.core.validators 6 | import django_countries.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('brambling', '0034_auto_20151104_0259'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='event', 18 | name='city', 19 | field=models.CharField(max_length=50, blank=True), 20 | preserve_default=True, 21 | ), 22 | migrations.AlterField( 23 | model_name='event', 24 | name='country', 25 | field=django_countries.fields.CountryField(default='US', max_length=2, blank=True), 26 | preserve_default=True, 27 | ), 28 | migrations.AlterField( 29 | model_name='event', 30 | name='state_or_province', 31 | field=models.CharField(max_length=50, verbose_name='state / province', blank=True), 32 | preserve_default=True, 33 | ), 34 | migrations.AlterField( 35 | model_name='organization', 36 | name='default_application_fee_percent', 37 | field=models.DecimalField(default=1.5, max_digits=5, decimal_places=2, validators=[django.core.validators.MaxValueValidator(100), django.core.validators.MinValueValidator(0)]), 38 | preserve_default=True, 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /brambling/api/v1/endpoints/itemimage.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from rest_framework import serializers, viewsets 3 | 4 | from brambling.api.v1.permissions import BaseEventPermission 5 | from brambling.models import ItemImage 6 | 7 | 8 | class ItemImagePermission(BaseEventPermission): 9 | def has_object_permission(self, request, view, itemimage): 10 | return self._has_event_permission(request, itemimage.item.event) 11 | 12 | 13 | class ItemImageSerializer(serializers.HyperlinkedModelSerializer): 14 | link = serializers.HyperlinkedIdentityField(view_name='itemimage-detail') 15 | resize_endpoint = serializers.SerializerMethodField() 16 | 17 | class Meta: 18 | model = ItemImage 19 | fields = ('id', 'link', 'item', 'order', 'image', 'resize_endpoint') 20 | 21 | def get_resize_endpoint(self, obj): 22 | request = self.context.get('request', None) 23 | 24 | assert request is not None, ( 25 | "`%s` requires the request in the serializer" 26 | " context. Add `context={'request': request}` when instantiating " 27 | "the serializer." % self.__class__.__name__ 28 | ) 29 | 30 | url = reverse('daguerre_ajax_adjustment_info', kwargs={'storage_path': obj.image.name}) 31 | 32 | return request.build_absolute_uri(url) 33 | 34 | 35 | class ItemImageViewSet(viewsets.ModelViewSet): 36 | queryset = ItemImage.objects.all() 37 | serializer_class = ItemImageSerializer 38 | permission_classes = [ItemImagePermission] 39 | -------------------------------------------------------------------------------- /brambling/templates/brambling/forms/home.html: -------------------------------------------------------------------------------- 1 | {% extends "floppyforms/layouts/bootstrap.html" %} 2 | 3 | {% load floppyforms %} 4 | 5 | {% block rows %} 6 | {% formconfig row using "brambling/forms/rows/bootstrap.html" %} 7 |
8 |
9 |

Basic Information

10 |
11 |
12 | {% formrow form.address %} 13 | {% formrow form.address_2 using "brambling/forms/rows/no_label.html" %} 14 |
15 |
16 | {% formrow form.city %} 17 |
18 |
19 | {% formrow form.state_or_province %} 20 |
21 |
22 | {% formrow form.zip_code with label="Zip Code"%} 23 |
24 |
{# /.row #} 25 | {% formrow form.country %} 26 | {% formrow form.housing_categories %} 27 | {% formrow form.public_transit_access %} 28 |
29 |
{# /.panel #} 30 |
31 |
32 |

Environmental factors

33 |
34 |
35 | {% formrow form.ef_present %} 36 | {% formrow form.ef_avoid %} 37 |
38 |
39 |
40 |
41 |

People

42 |
43 |
44 | {% formrow form.person_prefer %} 45 | {% formrow form.person_avoid %} 46 |
47 |
48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /brambling/templates/brambling/user/admin_events.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/user/__base_organize.html' %} 2 | 3 | {% load floppyforms humanize zenaida %} 4 | 5 | {% block title %}Events – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 | {{ block.super }} 9 |

Events

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% for event in admin_events %} 22 | 23 | 29 | 30 | 31 | 37 | 38 | {% empty %} 39 | 40 | 43 | 44 | {% endfor %} 45 | 46 |
EventOrganizationLast activity (UTC)
24 | 25 | {{ event.name }} 26 |
27 | {% include "brambling/event/_when.html" with event=event only %} 28 |
{{ event.organization.name }}{{ event.last_modified }} 32 | 33 | 34 | Settings 35 | 36 |
41 | It doesn't look like you have any events right now; go get started! 42 |
47 | 48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.python.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | django = "==1.9.*" 8 | pytz = "==2015.2" 9 | unicode-slugify = "==0.1.3" 10 | libsass = "==0.9.1" 11 | django-libsass = "==0.4" 12 | django-appconf = "==1.0.1" 13 | django-compressor = "==1.5" 14 | django-countries = "==3.4.1" 15 | django-filter = "==0.12.0" 16 | django-grappelli = "==2.8.3" 17 | dj-database-url = "==0.4.2" 18 | pillow = "==2.8.1" 19 | django-daguerre = "==2.2.*" 20 | django-floppyforms = "==1.6.1" 21 | django-localflavor = "==1.1" 22 | stripe = "==1.35.0" 23 | requests = "==2.21.0" 24 | django-talkback = "==0.1.2" 25 | djangorestframework = "==3.3.0" 26 | openpyxl = "==2.2.6" 27 | jdcal = "==1.0.1" 28 | unicodecsv = "==0.14.1" 29 | markdown = "==2.6.5" 30 | bleach = "*" 31 | django-bootstrap = {git = "https://github.com/littleweaver/django-bootstrap.git", editable = true, ref = "bootstrap3"} 32 | django-zenaida = {git = "git://github.com/dancerfly/django-zenaida.git", editable = true, ref = "master"} 33 | mock = "==1.0.1" 34 | factory-boy = "==2.4.1" 35 | tox = "==1.8.1" 36 | virtualenv = "==12.0.6" 37 | py = "==1.4.26" 38 | vcrpy = "==1.7.4" 39 | pyyaml = "==3.13" 40 | wrapt = "==1.10.5" 41 | "contextlib2" = "==0.4.0" 42 | sendgrid = "==2.2.1" 43 | sendgrid-django = "==2.0.0" 44 | smtpapi = "==0.3.1" 45 | python-http-client = "==1.2.3" 46 | gunicorn = "==19.9.0" 47 | django-debug-toolbar = "==1.9.*" 48 | django-debug-toolbar-template-timings = "==0.9.*" 49 | "psycopg2" = "==2.7.5" 50 | django-storages = "==1.6.6" 51 | "boto3" = "==1.7.59" 52 | 53 | [dev-packages] 54 | 55 | [requires] 56 | python_version = "2.7" 57 | -------------------------------------------------------------------------------- /brambling/api/v1/endpoints/event.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers, viewsets 2 | 3 | from brambling.api.v1.permissions import BaseEventPermission 4 | from brambling.models import ( 5 | DanceStyle, 6 | Event, 7 | ) 8 | 9 | 10 | class EventPermission(BaseEventPermission): 11 | def has_object_permission(self, request, view, event): 12 | # Disallow deletion of events (for now, just a blanket). 13 | if request.method == 'DELETE': 14 | return False 15 | 16 | return self._has_event_permission(request, event) 17 | 18 | 19 | class EventSerializer(serializers.HyperlinkedModelSerializer): 20 | """Serializes public data for an event.""" 21 | dance_styles = serializers.SlugRelatedField( 22 | slug_field='name', 23 | queryset=DanceStyle.objects.all(), 24 | many=True, 25 | ) 26 | link = serializers.HyperlinkedIdentityField(view_name='event-detail') 27 | 28 | class Meta: 29 | model = Event 30 | fields = ( 31 | 'id', 'link', 'name', 'slug', 'description', 'website_url', 32 | 'facebook_url', 'banner_image', 'city', 'state_or_province', 33 | 'country', 'start_date', 'end_date', 'start_time', 'end_time', 34 | 'dance_styles', 'has_dances', 'has_classes', 'liability_waiver', 35 | 'organization', 'collect_housing_data', 'collect_survey_data', 36 | 'timezone', 'currency', 'cart_timeout' 37 | ) 38 | 39 | 40 | class EventViewSet(viewsets.ModelViewSet): 41 | queryset = Event.objects.all() 42 | serializer_class = EventSerializer 43 | permission_classes = [EventPermission] 44 | -------------------------------------------------------------------------------- /brambling/templates/brambling/user/__base.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/3_9.html' %} 2 | 3 | {% load humanize %} 4 | 5 | {% block side %} 6 | {% url 'brambling_home' as brambling_home_url %} 7 | {% url 'brambling_user_account' as brambling_user_account_url %} 8 | {% url 'brambling_user_notifications' as brambling_user_notifications_url %} 9 | {% url 'brambling_user_billing' as brambling_user_billing_url %} 10 | {% url 'brambling_user_attendees' as brambling_user_attendees_url %} 11 |
12 |
13 |

Settings

14 |
15 | 22 |
23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /brambling/migrations/0002_auto_20150502_0912.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | # The data is lost, really, but we can at least make this 8 | # "reversible" for testing 9 | def event_pass_backwards(apps, schema_editor): 10 | Attendee = apps.get_model('brambling', 'Attendee') 11 | for attendee in Attendee.objects.all(): 12 | attendee.event_pass = attendee.bought_items.all()[0] 13 | attendee.save() 14 | 15 | 16 | class Migration(migrations.Migration): 17 | 18 | dependencies = [ 19 | ('brambling', '0001_initial'), 20 | ] 21 | 22 | operations = [ 23 | migrations.AlterField( 24 | model_name='item', 25 | name='category', 26 | field=models.CharField(default='merch', max_length=7, choices=[(b'merch', 'Merchandise'), (b'comp', 'Competition'), (b'class', 'Class/Lesson a la carte'), (b'pass', 'Pass')]), 27 | preserve_default=True, 28 | ), 29 | migrations.AlterField( 30 | model_name='attendee', 31 | name='event_pass', 32 | field=models.OneToOneField(related_name=b'event_pass_for', to='brambling.BoughtItem', blank=True, null=True), 33 | preserve_default=True, 34 | ), 35 | migrations.RunPython(lambda x, y: None, event_pass_backwards), 36 | migrations.RemoveField( 37 | model_name='attendee', 38 | name='event_pass', 39 | ), 40 | migrations.RemoveField( 41 | model_name='item', 42 | name='category', 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/item_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/event/organizer/__base.html' %} 2 | 3 | {% load static %} 4 | 5 | {% block title %}Items – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 | {{ block.super }} 9 | 10 |
11 | {% if items %} 12 |
13 |
14 |
15 |

Items

16 |
17 |
18 | {% for item in items %} 19 | {{ item.name }} {{ item.option_count }} option{{ item.option_count|pluralize }} 20 | {% endfor %} 21 |
22 |
23 |
24 | {% endif %} 25 | {% if 'edit' in event_permissions %} 26 |
27 |
28 |
29 |

Create Item

30 |
31 |
32 | {% url 'brambling_item_create' event_slug=event.slug organization_slug=event.organization.slug as action %} 33 | {% include "brambling/event/organizer/_item_form.html" with action=action %} 34 |
35 |
36 |
37 | {% endif %} 38 | {% endblock %} 39 | 40 | {% block javascripts %} 41 | {{ block.super }} 42 | {% if 'edit' in event_permissions %} 43 | {% include "brambling/event/organizer/_item_form_js.html" %} 44 | {% endif %} 45 | {% endblock %} 46 | -------------------------------------------------------------------------------- /brambling/api/v1/tests/test_ordersearch.py: -------------------------------------------------------------------------------- 1 | from rest_framework import status 2 | from rest_framework.test import APITestCase 3 | 4 | from brambling.tests.factories import ( 5 | EventFactory, 6 | OrderFactory, 7 | PersonFactory, 8 | ) 9 | 10 | 11 | class OrderSearchTestCase(APITestCase): 12 | def setUp(self): 13 | self.person = PersonFactory(password='password', is_superuser=True) 14 | self.client.login(username=self.person.email, password='password') 15 | self.event = EventFactory() 16 | self.order = OrderFactory(event=self.event) 17 | 18 | def test_get__no_permissions(self): 19 | self.person.is_superuser = False 20 | self.person.save() 21 | 22 | response = self.client.get( 23 | '/api/v1/ordersearch/', { 24 | 'event': self.event.pk, 25 | }, 26 | ) 27 | 28 | self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) 29 | 30 | def test_get__no_event(self): 31 | response = self.client.get( 32 | '/api/v1/ordersearch/', 33 | ) 34 | 35 | self.assertEqual(response.status_code, status.HTTP_200_OK) 36 | self.assertEqual(len(response.data), 0) 37 | 38 | def test_get__with_event(self): 39 | event2 = EventFactory() 40 | OrderFactory(event=event2) 41 | response = self.client.get( 42 | '/api/v1/ordersearch/', { 43 | 'event': self.event.pk, 44 | }, 45 | ) 46 | 47 | self.assertEqual(response.status_code, status.HTTP_200_OK) 48 | self.assertEqual(len(response.data), 1) 49 | self.assertEqual(response.data[0]['id'], self.order.pk) 50 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/order/_attendee_summary.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Attendees 5 |
6 |
7 |
    8 | {% for attendee in attendees %} 9 |
  • 10 |

    11 | {{ attendee.get_full_name }} 12 | 13 | {% block actions %} 14 | {% url "brambling_event_attendee_edit" pk=attendee.pk event_slug=event.slug organization_slug=event.organization.slug as attendee_url %} 15 | 16 | 17 | Edit {{ attendee.get_full_name }} 18 | 19 | {% endblock %} 20 |

    21 |

    22 | {% for item in attendee.bought_items.all %} 23 | {% if item.status == item.RESERVED or item.status == item.UNPAID or item.status == item.BOUGHT %} 24 | {{ item.item_option_name }} 25 | ({{ item.item_name }}) 26 | {% if event.transfers_allowed %} 27 | {% url "brambling_event_order_transfer" event_slug=event.slug organization_slug=event.organization.slug as transfer_url %} 28 | {% if item.can_transfer %} 29 | 30 | 31 | Transfer 32 | {% if not user.is_authenticated %} 33 | (account required) 34 | {% endif %} 35 | 36 | {% endif %} 37 | {% endif %} 38 |
    39 | {% endif %} 40 | {% endfor %} 41 |

    42 |
  • 43 | {% endfor %} 44 |
45 |
46 | -------------------------------------------------------------------------------- /brambling/templates/brambling/_organization_list-group-item.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{ organization.name }}
5 |
6 |
7 | 8 |
9 | {% if organization.city %} 10 |
11 | {% include "brambling/event/_where.html" with event=organization %} 12 |
13 | {% endif %} 14 |
15 |
{# /.col-sm-9 #} 16 | {# /.col-sm-3.hidden-xs #} 26 |
{# /.row #} 27 | {# /.visible-xs #} 35 |
36 | -------------------------------------------------------------------------------- /brambling/tests/functional/fixtures/stripe_webhook_charge_not_found.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: ['*/*'] 6 | Accept-Encoding: ['gzip, deflate'] 7 | Authorization: [Bearer sk_test_OBAqgCaFyOUaaFwHJirSbg9D] 8 | Connection: [keep-alive] 9 | Stripe-Version: ['2016-03-07'] 10 | User-Agent: [Stripe/v1 PythonBindings/1.35.0] 11 | X-Stripe-Client-User-Agent: ['{"lang": "python", "publisher": "stripe", "httplib": 12 | "requests", "uname": "Linux brux 4.4.0-78-generic #99-Ubuntu SMP Thu Apr 13 | 27 15:29:09 UTC 2017 x86_64 x86_64", "lang_version": "2.7.10", "platform": 14 | "Linux-4.4.0-78-generic-x86_64-with-Ubuntu-16.04-xenial", "bindings_version": 15 | "1.35.0"}'] 16 | method: GET 17 | uri: https://api.stripe.com/v1/events/evt_123_event_id 18 | response: 19 | body: {string: !!python/unicode "{\n \"error\": {\n \"type\": \"invalid_request_error\"\ 20 | ,\n \"message\": \"No such event: evt_123_event_id\",\n \"param\": \"\ 21 | id\"\n }\n}\n"} 22 | headers: 23 | access-control-allow-credentials: ['true'] 24 | access-control-allow-methods: ['GET, POST, HEAD, OPTIONS, DELETE'] 25 | access-control-allow-origin: ['*'] 26 | access-control-max-age: ['300'] 27 | cache-control: ['no-cache, no-store'] 28 | connection: [keep-alive] 29 | content-length: ['126'] 30 | content-type: [application/json] 31 | date: ['Tue, 16 May 2017 20:51:41 GMT'] 32 | request-id: [req_AfXjxrwLkn6wpn] 33 | server: [nginx] 34 | stripe-version: ['2016-03-07'] 35 | status: {code: 404, message: Not Found} 36 | version: 1 37 | -------------------------------------------------------------------------------- /brambling/migrations/0015_auto_20150528_1806.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.db.models.deletion 6 | import django.core.validators 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('brambling', '0014_remove_attendee_person_confirmed'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='boughtitem', 18 | name='item_description', 19 | field=models.TextField(blank=True), 20 | preserve_default=True, 21 | ), 22 | migrations.AddField( 23 | model_name='boughtitem', 24 | name='item_name', 25 | field=models.CharField(default='', max_length=30), 26 | preserve_default=False, 27 | ), 28 | migrations.AddField( 29 | model_name='boughtitem', 30 | name='item_option_name', 31 | field=models.CharField(default='', max_length=30), 32 | preserve_default=False, 33 | ), 34 | migrations.AddField( 35 | model_name='boughtitem', 36 | name='price', 37 | field=models.DecimalField(default=0, max_digits=6, decimal_places=2, validators=[django.core.validators.MinValueValidator(0)]), 38 | preserve_default=False, 39 | ), 40 | migrations.AlterField( 41 | model_name='boughtitem', 42 | name='item_option', 43 | field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='brambling.ItemOption', null=True), 44 | preserve_default=True, 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /brambling/api/v1/endpoints/ordersearch.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets, filters 2 | from rest_framework.permissions import BasePermission 3 | 4 | from brambling.api.v1.endpoints.order import OrderSerializer 5 | from brambling.models import Order, Event 6 | 7 | 8 | class OrderSearchPermission(BasePermission): 9 | def has_permission(self, request, view): 10 | "Make sure the event is editable by the user trying to view orders." 11 | event = view.get_event() 12 | return request.user.has_perm('edit', event) 13 | 14 | 15 | class OrderSearchViewSet(viewsets.ReadOnlyModelViewSet): 16 | "A ViewSet that filters orders based on a single search term." 17 | queryset = Order.objects.all() 18 | serializer_class = OrderSerializer 19 | filter_backends = (filters.SearchFilter,) 20 | permission_classes = [OrderSearchPermission] 21 | search_fields = ( 22 | "code", "person__first_name", "person__middle_name", 23 | "person__last_name", "attendees__first_name", 24 | "attendees__middle_name", "attendees__last_name" 25 | ) 26 | 27 | def get_event(self): 28 | event_id = self.request.query_params.get('event', None) 29 | return Event.objects.filter(pk=event_id).first() 30 | 31 | def get_queryset(self): 32 | "Filter orders down to those which are for the specific event provided." 33 | 34 | qs = super(OrderSearchViewSet, self).get_queryset().prefetch_related( 35 | 'bought_items', 36 | ).select_related( 37 | 'event', 'person', 'eventhousing', 38 | ) 39 | 40 | event = self.get_event() 41 | if not event: 42 | return qs.none() 43 | 44 | return qs.filter(event=event) 45 | -------------------------------------------------------------------------------- /brambling/api/v1/endpoints/itemoption.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers, viewsets 2 | 3 | from brambling.api.v1.permissions import BaseEventPermission 4 | from brambling.models import ( 5 | BoughtItem, 6 | ItemOption, 7 | ) 8 | 9 | 10 | class ItemOptionPermission(BaseEventPermission): 11 | def has_object_permission(self, request, view, itemoption): 12 | return self._has_event_permission(request, itemoption.item.event) 13 | 14 | 15 | class ItemOptionSerializer(serializers.HyperlinkedModelSerializer): 16 | link = serializers.HyperlinkedIdentityField(view_name='itemoption-detail') 17 | taken = serializers.SerializerMethodField() 18 | 19 | class Meta: 20 | model = ItemOption 21 | fields = ('id', 'link', 'item', 'name', 'price', 'total_number', 22 | 'available_start', 'available_end', 'remaining_display', 23 | 'order', 'taken') 24 | 25 | def get_taken(self, obj): 26 | if hasattr(obj, 'taken'): 27 | return obj.taken 28 | return obj.boughtitem_set.exclude(status=BoughtItem.REFUNDED).count() 29 | 30 | 31 | class ItemOptionViewSet(viewsets.ModelViewSet): 32 | queryset = ItemOption.objects.all() 33 | serializer_class = ItemOptionSerializer 34 | permission_classes = [ItemOptionPermission] 35 | 36 | def get_queryset(self): 37 | qs = super(ItemOptionViewSet, self).get_queryset() 38 | qs = qs.extra(select={ 39 | 'taken': """ 40 | SELECT COUNT(*) FROM brambling_boughtitem WHERE 41 | brambling_boughtitem.item_option_id = brambling_itemoption.id AND 42 | brambling_boughtitem.status != 'refunded' AND 43 | brambling_boughtitem.status != 'transferred' 44 | """ 45 | }) 46 | return qs 47 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/modules/_navbar.sass: -------------------------------------------------------------------------------- 1 | // See height and colors defined in _variables.sass 2 | 3 | .navbar-fixed-top:hover 4 | top: 0 5 | 6 | .navbar-dancerfly 7 | border: 0 8 | border-bottom: 1px solid $btn-primary-border 9 | font-family: $headings-font-family 10 | background-color: $navbar-dancerfly-bg 11 | .navbar-nav > li > a 12 | color: $navbar-dancerfly-link-color 13 | &:hover 14 | color: $navbar-dancerfly-link-hover-color 15 | background-color: $navbar-dancerfly-link-hover-bg 16 | &:active, &:focus 17 | color: $navbar-dancerfly-link-active-color 18 | background-color: $navbar-dancerfly-link-active-bg 19 | .navbar-brand 20 | color: $navbar-dancerfly-brand-color 21 | &:hover 22 | color: $navbar-dancerfly-brand-hover-color 23 | background-color: $navbar-dancerfly-brand-hover-bg 24 | .navbar-toggle 25 | border-color: $navbar-dancerfly-toggle-border-color 26 | > .icon-bar 27 | background-color: $navbar-dancerfly-toggle-icon-bar-bg 28 | &:hover 29 | background-color: $navbar-dancerfly-toggle-hover-bg 30 | .navbar-nav > .open > a 31 | &, &:hover, &:focus 32 | background: rgba(0, 0, 0, .25) 33 | .navbar-collapse 34 | @media (min-width: $screen-sm-min) 35 | padding: 0 36 | 37 | .nav-tabs 38 | margin-bottom: $line-height-computed 39 | 40 | .navbar-hugged 41 | border-bottom-width: 0 42 | > .container 43 | border-bottom: 1px solid lighten($yellow-light,10%) 44 | &.navbar-hugged-remove 45 | border-bottom-width: 1 46 | > .container 47 | border-bottom-width: 0 48 | -------------------------------------------------------------------------------- /brambling/migrations/0052_auto_20160318_0712.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.core.validators 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('brambling', '0051_auto_20160228_0106'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='boughtitemdiscount', 17 | name='amount', 18 | field=models.DecimalField(max_digits=6, decimal_places=2, validators=[django.core.validators.MinValueValidator(0)]), 19 | preserve_default=True, 20 | ), 21 | migrations.AlterField( 22 | model_name='discount', 23 | name='amount', 24 | field=models.DecimalField(verbose_name='discount value', max_digits=6, decimal_places=2, validators=[django.core.validators.MinValueValidator(0)]), 25 | preserve_default=True, 26 | ), 27 | migrations.AlterField( 28 | model_name='transaction', 29 | name='amount', 30 | field=models.DecimalField(default=0, max_digits=9, decimal_places=2), 31 | preserve_default=True, 32 | ), 33 | migrations.AlterField( 34 | model_name='transaction', 35 | name='application_fee', 36 | field=models.DecimalField(default=0, max_digits=9, decimal_places=2), 37 | preserve_default=True, 38 | ), 39 | migrations.AlterField( 40 | model_name='transaction', 41 | name='processing_fee', 42 | field=models.DecimalField(default=0, max_digits=9, decimal_places=2), 43 | preserve_default=True, 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/_quickfind_ractive.html: -------------------------------------------------------------------------------- 1 | {# This whole template is verbatim - use ractive, not Django. #} 2 | 3 | {% verbatim %} 4 |
5 |
6 |

Find Order

7 |
8 |
9 |
10 |
11 | 12 | {{#if query && !loaded}} 13 |
14 | 15 |
16 | {{/if}} 17 | 18 |
19 |
20 | {{#if query}} 21 | {{#results}} 22 | {{!TODO: Make this URL use Django's URL reversing somehow.}} 23 | 24 |
Order {{{ filters.highlight(query, code) }}}
25 |
26 | {{#if person}} 27 | Purchaser: {{{ filters.highlight(query, person) }}} 28 | {{/if}} 29 | {{!TODO: Render attendees here too.}} 30 |
31 |
32 | {{elseif loaded}} 33 |
34 |
35 | No results for “{{ query }}”. 36 |
37 |
38 | {{/results}} 39 | {{/if}} 40 |
41 |
42 | {% endverbatim %} 43 | -------------------------------------------------------------------------------- /brambling/static/brambling/sass/_variables.sass: -------------------------------------------------------------------------------- 1 | $yellow-dark: #de8e0f 2 | $yellow-darker: saturate(darken($yellow-dark,10%),10%) 3 | $yellow-light: #E8A500 4 | $blue-light: #5FD7FD 5 | $blue-dark: #208AFE 6 | $gray-lighter: #E5E5E5 7 | $gray-dark: #333 8 | $brand-primary: $yellow-dark 9 | 10 | $font-family-serif: Georgia, "Times New Roman", Times, serif !default 11 | $headings-font-family: "St Ryde", $font-family-serif 12 | $headings-font-weight-bold: 700 13 | $headings-font-weight-normal: 400 14 | $headings-font-weight: $headings-font-weight-normal 15 | 16 | $component-active-bg: $gray-lighter 17 | $component-active-color: $gray-dark 18 | 19 | $navbar-height: 40px 20 | 21 | $navbar-dancerfly-bg: $brand-primary 22 | $navbar-dancerfly-link-color: white 23 | $navbar-dancerfly-link-hover-color: $navbar-dancerfly-link-color 24 | $navbar-dancerfly-link-hover-bg: rgba(0, 0, 0, .15) 25 | $navbar-dancerfly-link-active-color: $navbar-dancerfly-link-color 26 | $navbar-dancerfly-link-active-bg: $navbar-dancerfly-link-hover-bg 27 | $navbar-dancerfly-brand-color: $navbar-dancerfly-link-color 28 | $navbar-dancerfly-brand-hover-color: $navbar-dancerfly-link-color 29 | $navbar-dancerfly-brand-hover-bg: $navbar-dancerfly-link-hover-bg 30 | $navbar-dancerfly-toggle-hover-bg: $yellow-light 31 | $navbar-dancerfly-toggle-icon-bar-bg: $yellow-darker 32 | $navbar-dancerfly-toggle-border-color: $yellow-darker 33 | 34 | $panel-primary-text: darken(desaturate($brand-primary,5%),25%) 35 | $panel-primary-heading-bg: lighten($brand-primary,25%) 36 | $panel-primary-border: darken(adjust-hue($panel-primary-heading-bg, -10), 5%) 37 | 38 | $code-color: $gray-dark 39 | 40 | $screen-xxs-max: 480px 41 | $screen-xs-min: $screen-xxs-max + 1 42 | 43 | $dancers-event-banner-url: url("/static/brambling/images/banner.jpg") 44 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/order/transfer.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/event/order/__base.html' %} 2 | 3 | {% load floppyforms %} 4 | 5 | {% block title %}Transfer item – {{ block.super }}{% endblock %} 6 | 7 | {% block main %} 8 | {% with next_step=1 %} 9 | {{ block.super }} 10 | {% endwith %} 11 | 12 | {% if not user.is_authenticated %} 13 |
14 | Sorry! 15 | You need to create an account to transfer an item. 16 | You'll be able to claim this order right away. 17 |
18 | {% endif %} 19 | 20 |
21 |
22 |

23 | Clicking the "Transfer it!" button will send a confirmation link to the specified email address. Once the recipient accepts the transfer, it cannot be undone. 24 |

25 |

26 | If transferring an item leaves an attendee with no items, that attendee will be removed from your order. 27 |

28 | 29 |
30 | {% csrf_token %} 31 |
32 | {% form form using %} 33 | {% formrow form.bought_item with label="Item to transfer" %} 34 | {% formrow form.email with label="Send item to..." help_text="Email address" %} 35 | {% endform %} 36 | 37 | 38 | 39 | {% url "brambling_event_order_summary" event_slug=event.slug organization_slug=event.organization.slug as cancel_url %} 40 | 41 | 42 | Wait, never mind 43 | 44 |
45 |
46 |
47 |
48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /brambling/migrations/0017_auto_20150528_1846.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.db.models.deletion 6 | import django.core.validators 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('brambling', '0016_auto_20150528_1808'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='boughtitemdiscount', 18 | name='amount', 19 | field=models.DecimalField(default=0, max_digits=5, decimal_places=2, validators=[django.core.validators.MinValueValidator(0)]), 20 | preserve_default=False, 21 | ), 22 | migrations.AddField( 23 | model_name='boughtitemdiscount', 24 | name='code', 25 | field=models.CharField(default='', max_length=20), 26 | preserve_default=False, 27 | ), 28 | migrations.AddField( 29 | model_name='boughtitemdiscount', 30 | name='discount_type', 31 | field=models.CharField(default='flat', max_length=7, choices=[('flat', 'Flat'), ('percent', 'Percent')]), 32 | preserve_default=True, 33 | ), 34 | migrations.AddField( 35 | model_name='boughtitemdiscount', 36 | name='name', 37 | field=models.CharField(default='', max_length=40), 38 | preserve_default=False, 39 | ), 40 | migrations.AlterField( 41 | model_name='boughtitemdiscount', 42 | name='discount', 43 | field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='brambling.Discount', null=True), 44 | preserve_default=True, 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/order/_use_discount_js.html: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /brambling/migrations/0027_saved_attendee_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | def create(person, SavedAttendee): 8 | saved = SavedAttendee.objects.create( 9 | person=person, 10 | email=person.email, 11 | phone=person.phone, 12 | given_name=person.given_name, 13 | middle_name=person.middle_name, 14 | surname=person.surname, 15 | name_order=person.name_order, 16 | person_prefer=person.person_prefer, 17 | person_avoid=person.person_avoid, 18 | other_needs=person.other_needs, 19 | ) 20 | saved.ef_cause = person.ef_cause.all() 21 | saved.ef_avoid = person.ef_avoid.all() 22 | saved.housing_prefer = person.housing_prefer.all() 23 | 24 | 25 | def should_create_for(person): 26 | if person.phone or person.other_needs or person.person_prefer or person.person_avoid: 27 | return True 28 | if person.ef_cause.all() or person.ef_avoid.all() or person.housing_prefer.all(): 29 | return True 30 | return False 31 | 32 | 33 | def create_saved_attendees(apps, schema_editor): 34 | SavedAttendee = apps.get_model('brambling', 'SavedAttendee') 35 | Person = apps.get_model('brambling', 'Person') 36 | 37 | people = Person.objects.prefetch_related( 38 | 'ef_cause', 39 | 'ef_avoid', 40 | 'housing_prefer', 41 | ) 42 | 43 | for person in people: 44 | if should_create_for(person): 45 | create(person, SavedAttendee) 46 | 47 | 48 | class Migration(migrations.Migration): 49 | 50 | dependencies = [ 51 | ('brambling', '0026_savedattendee'), 52 | ] 53 | 54 | operations = [ 55 | migrations.RunPython(create_saved_attendees, lambda *a, **k: None) 56 | ] 57 | -------------------------------------------------------------------------------- /brambling/templates/brambling/creditcard_add.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/layouts/12_xs.html' %} 2 | 3 | {% load floppyforms static %} 4 | 5 | {% block title %}Add a card – {{ block.super }}{% endblock %} 6 | 7 | {% block javascripts %} 8 | {{ block.super }} 9 | {% if api_type == TEST or request.is_secure %} 10 | 29 | {% include "brambling/_stripe_js.html" %} 30 | {% endif %} 31 | 32 | {% endblock %} 33 | 34 | {% block main %} 35 |

Add a card

36 | 37 | {% for error in errors %} 38 |
39 | {{ error }} 40 |
41 | {% endfor %} 42 | 43 | {% include 'brambling/_stripe_form.html' with api_type=api_type %} 44 | 45 |

46 | 47 | Cancel 48 |

49 | 50 |
51 | {% csrf_token %} 52 |
53 | {% endblock %} 54 | -------------------------------------------------------------------------------- /brambling/templates/brambling/mail/daily_digest/body_plaintext.txt: -------------------------------------------------------------------------------- 1 | {% load brambling_plaintext zenaida %}{% regroup transactions by event as event_list %}{% autoescape off %}Congratulations! Your events have made the following sales since {{ cutoff }} 2 | {% for event in event_list %} 3 | 4 | {{ event.grouper.name }} 5 | {{ protocol }}://{{ site.domain }}{% url "brambling_event_summary" event_slug=event.grouper.slug organization_slug=event.grouper.organization.slug %} 6 | 7 | {{ "Code"|ljust:15 }} | {{ "Amount"|ljust:13 }} | {{ "Items"|ljust:12 }} 8 | {% for transaction in event.list %}{% with items=transaction.bought_items.all %}================+===============+=============================== 9 | {{ transaction.order.code|ljust:15 }} | {{ transaction.amount|format_money:transaction.event.currency|ljust:13 }} | {% if items.0 %}{% filter truncatechars:30 %}{{ items.0.item_name }} ({{ items.0.item_option_name }}){% endfilter %}{% endif %} 10 | {{ transaction.timestamp|date:"DATE_FORMAT"|ljust:15 }} | {% if transaction.is_unconfirmed_check %}{{ "Check pending"|ljust:13 }}{% else %}{{ ""|ljust:13 }}{% endif %} | {% if items.1 %}{% filter truncatechars:30 %}{{ items.1.item_name }} ({{ items.1.item_option_name }}){% endfilter %}{% endif %} 11 | {{ transaction.timestamp|date:"TIME_FORMAT"|ljust:15 }} | {{ ""|ljust:13 }} | {% if items.2 %}{% filter truncatechars:30 %}{{ items.2.item_name }} ({{ items.2.item_option_name }}){% endfilter %}{% endif %} 12 | {% for item in items|slice:"3:" %} 13 | {{ ""|ljust:15 }} | {{ ""|ljust:13 }} | {{ item.item_name }} ({{ item.item_option_name }}) 14 | {% endfor %}{% endwith %}{% endfor %}{% endfor %}{% endautoescape %} 15 | 16 | You can update your notification settings here: {{ protocol }}://{{ site.domain }}{% url "brambling_user_notifications" %} 17 | 18 | Dancerfly (dancerfly.com) is a project of Harris Lapiroff 19 | -------------------------------------------------------------------------------- /brambling/templates/brambling/forms/customformfield.html: -------------------------------------------------------------------------------- 1 | {% extends "floppyforms/layouts/bootstrap.html" %} 2 | 3 | {% load floppyforms tz %} 4 | {% formconfig row using "brambling/forms/rows/bootstrap.html" %} 5 | 6 | {% block rows %} 7 |
8 | {% if 'edit' in event_permissions %} 9 | 12 | {% endif %} 13 |
14 | {% formrow form.name using "brambling/forms/rows/no_label.html" with placeholder="Do you snore?" %} 15 |
16 |
17 | {% formrow form.field_type using "brambling/forms/rows/no_label.html" %} 18 |
19 |
20 | {% formrow form.required %} 21 |
22 |
23 |
24 |
25 | 26 |
27 |
28 | {% formrow form.default %} 29 |
30 |
31 | {% formrow form.help_text %} 32 |
33 |
34 | 35 | 36 | 41 | 42 | {% for field in form.hidden_fields %} 43 | {% formfield field %} 44 | {% endfor %} 45 | {# The following form fields are controlled with javascript: #} 46 |
47 | {% formrow form.index %} 48 | {# Don't render delete box if this is a dynamic form. We'll delete those by removing the DOM element with javascript. #} 49 | {% if not "__prefix__" in form.prefix %}{% formrow form.DELETE %}{% endif %} 50 |
51 | {% endblock %} 52 | -------------------------------------------------------------------------------- /brambling/api/v1/endpoints/organization.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers, viewsets 2 | from rest_framework.permissions import BasePermission, SAFE_METHODS 3 | 4 | from brambling.models import ( 5 | DanceStyle, 6 | Organization, 7 | ) 8 | 9 | 10 | class OrganizationPermission(BasePermission): 11 | def has_permission(self, request, view): 12 | # For now, disallow creation via the API. 13 | if request.method == 'POST': 14 | return False 15 | return True 16 | 17 | def has_object_permission(self, request, view, org): 18 | # Anyone can get a list or detail view. 19 | if request.method in SAFE_METHODS: 20 | return True 21 | 22 | # Anyone who can edit the org also has RUD permissions. 23 | if request.user.has_perm('edit', org): 24 | return True 25 | 26 | # Disallow deletion (for now, just a blanket). 27 | if request.method == 'DELETE': 28 | return False 29 | 30 | return False 31 | 32 | 33 | class OrganizationSerializer(serializers.HyperlinkedModelSerializer): 34 | """Serializes public data for an organization.""" 35 | dance_styles = serializers.SlugRelatedField( 36 | slug_field='name', 37 | queryset=DanceStyle.objects.all(), 38 | many=True, 39 | ) 40 | link = serializers.HyperlinkedIdentityField(view_name='organization-detail') 41 | 42 | class Meta: 43 | model = Organization 44 | fields = ( 45 | 'id', 'link', 'name', 'slug', 'description', 'website_url', 'facebook_url', 46 | 'banner_image', 'city', 'state_or_province', 'country', 47 | 'dance_styles', 48 | ) 49 | 50 | 51 | class OrganizationViewSet(viewsets.ModelViewSet): 52 | queryset = Organization.objects.all() 53 | serializer_class = OrganizationSerializer 54 | permission_classes = [OrganizationPermission] 55 | -------------------------------------------------------------------------------- /brambling/api/v1/tests/test_attendee.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | from django.test import TestCase, RequestFactory 3 | 4 | from brambling.api.v1.endpoints.attendee import AttendeeViewSet 5 | from brambling.models import EventMember 6 | from brambling.tests.factories import ( 7 | EventFactory, 8 | OrderFactory, 9 | AttendeeFactory, 10 | PersonFactory, 11 | ) 12 | 13 | 14 | class AttendeeViewSetTestCase(TestCase): 15 | def setUp(self): 16 | self.factory = RequestFactory() 17 | 18 | def test_queryset_distinct(self): 19 | """ 20 | For authenticated users, make sure the qs is distinct. 21 | Specifically, having multiple additional editors on 22 | the event shouldn't cause duplication issues. 23 | """ 24 | person = PersonFactory() 25 | editor1 = PersonFactory() 26 | editor2 = PersonFactory() 27 | editor3 = PersonFactory() 28 | event = EventFactory(collect_housing_data=False) 29 | EventMember.objects.create( 30 | person=editor1, 31 | event=event, 32 | role=EventMember.EDIT, 33 | ) 34 | EventMember.objects.create( 35 | person=editor2, 36 | event=event, 37 | role=EventMember.EDIT, 38 | ) 39 | EventMember.objects.create( 40 | person=editor3, 41 | event=event, 42 | role=EventMember.EDIT, 43 | ) 44 | order = OrderFactory(event=event, person=person) 45 | att1 = AttendeeFactory(order=order) 46 | att2 = AttendeeFactory(order=order) 47 | att3 = AttendeeFactory(order=order) 48 | 49 | viewset = AttendeeViewSet() 50 | viewset.request = self.factory.get('/') 51 | viewset.request.user = person 52 | 53 | qs = viewset.get_queryset() 54 | self.assertEqual(len(qs), 3) 55 | self.assertEqual(set(qs), set((att1, att2, att3))) 56 | -------------------------------------------------------------------------------- /brambling/api/v1/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url, include 2 | from rest_framework.routers import DefaultRouter 3 | 4 | from brambling.api.v1.endpoints.attendee import AttendeeViewSet 5 | from brambling.api.v1.endpoints.boughtitem import BoughtItemViewSet 6 | from brambling.api.v1.endpoints.dancestyle import DanceStyleViewSet 7 | from brambling.api.v1.endpoints.environmentalfactor import EnvironmentalFactorViewSet 8 | from brambling.api.v1.endpoints.event import EventViewSet 9 | from brambling.api.v1.endpoints.eventhousing import EventHousingViewSet 10 | from brambling.api.v1.endpoints.housingcategory import HousingCategoryViewSet 11 | from brambling.api.v1.endpoints.item import ItemViewSet 12 | from brambling.api.v1.endpoints.itemimage import ItemImageViewSet 13 | from brambling.api.v1.endpoints.itemoption import ItemOptionViewSet 14 | from brambling.api.v1.endpoints.order import OrderViewSet 15 | from brambling.api.v1.endpoints.ordersearch import OrderSearchViewSet 16 | from brambling.api.v1.endpoints.organization import OrganizationViewSet 17 | 18 | 19 | router = DefaultRouter() 20 | router.register('attendee', AttendeeViewSet) 21 | router.register('boughtitem', BoughtItemViewSet) 22 | router.register('dancestyle', DanceStyleViewSet) 23 | router.register('environmentalfactor', EnvironmentalFactorViewSet) 24 | router.register('event', EventViewSet) 25 | router.register('eventhousing', EventHousingViewSet) 26 | router.register('housingcategory', HousingCategoryViewSet) 27 | router.register('item', ItemViewSet) 28 | router.register('itemimage', ItemImageViewSet) 29 | router.register('itemoption', ItemOptionViewSet) 30 | router.register('order', OrderViewSet) 31 | router.register('ordersearch', OrderSearchViewSet, base_name='ordersearch') 32 | router.register('organization', OrganizationViewSet) 33 | 34 | urlpatterns = [ 35 | url(r'^', include(router.urls)), 36 | url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), 37 | ] 38 | -------------------------------------------------------------------------------- /brambling/migrations/0009_set_stripe_metadata.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | import warnings 4 | 5 | from django.db import migrations 6 | 7 | import stripe 8 | 9 | 10 | def set_stripe_metadata(apps, schema_editor): 11 | # Try to set remote metadata for all recorded transactions. 12 | # But don't get too upset if it all fails ;) 13 | Transaction = apps.get_model('brambling', 'Transaction') 14 | transactions = Transaction.objects.select_related( 15 | 'order__event__organization' 16 | ).filter( 17 | method='stripe', 18 | ) 19 | for txn in transactions: 20 | order = txn.order 21 | event = order.event 22 | organization = event.organization 23 | if txn.api_type == 'test' and organization.stripe_test_access_token: 24 | stripe.api_key = organization.stripe_test_access_token 25 | elif txn.api_type == 'live' and organization.stripe_access_token: 26 | stripe.api_key = organization.stripe_access_token 27 | try: 28 | if txn.transaction_type == 'purchase': 29 | remote = stripe.Charge.retrieve(txn.remote_id) 30 | elif txn.transaction_type == 'refund': 31 | ch = stripe.Charge.retrieve(txn.related_transaction.remote_id) 32 | remote = ch.refunds.retrieve(txn.remote_id) 33 | else: 34 | continue 35 | remote.metadata = { 36 | 'order': order.code, 37 | 'event': event.name, 38 | } 39 | remote.save() 40 | except stripe.InvalidRequestError, e: 41 | warnings.warn("Updating metadata failed: {}".format(e.message)) 42 | 43 | 44 | class Migration(migrations.Migration): 45 | 46 | dependencies = [ 47 | ('brambling', '0008_auto_20150520_0253'), 48 | ] 49 | 50 | operations = [ 51 | migrations.RunPython(set_stripe_metadata, lambda *a, **k: None), 52 | ] 53 | -------------------------------------------------------------------------------- /brambling/templates/brambling/_event_list-group-item.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{ event.name }}
5 |
6 |
7 | 8 |
9 |
10 | {% include "brambling/event/_when.html" %} 11 |
12 |
13 | {% if event.city and event.state_or_province and event.country %} 14 |
15 |
16 | 17 |
18 |
19 | {% include "brambling/event/_where.html" %} 20 |
21 |
22 | {% endif %} 23 |
{# /.col-sm-9 #} 24 | {# /.col-sm-3.hidden-xs #} 34 |
{# /.row #} 35 | {# /.visible-xs #} 43 |
44 | -------------------------------------------------------------------------------- /brambling/templates/brambling/event/organizer/__base.html: -------------------------------------------------------------------------------- 1 | {% extends 'brambling/__base_fluid.html' %} 2 | 3 | {% block title %}{{ event.name }} – {{ block.super }}{% endblock %} 4 | 5 | {% block top %} 6 | {{ block.super }} 7 | {% include "brambling/event/organizer/_header.html" %} 8 | {% endblock %} 9 | 10 | {% block content %} 11 |
12 |
13 | {% include "brambling/event/organizer/_nav.html" %} 14 |
15 |
16 | {% block messages %} 17 | {% include "brambling/layouts/_messages.html" %} 18 | {% endblock %} 19 | {% block main %}{% endblock %} 20 |
21 |
22 | {% endblock %} 23 | 24 | {% block javascripts %} 25 | {{ block.super }} 26 | 64 | {% endblock %} 65 | -------------------------------------------------------------------------------- /brambling/tests/functional/test_finance_table.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from brambling.views.utils import FinanceTable 4 | from brambling.tests.factories import (TransactionFactory, EventFactory, 5 | PersonFactory, OrderFactory) 6 | 7 | 8 | class FinanceTableTestCase(TestCase): 9 | 10 | def setUp(self): 11 | self.order = OrderFactory(code='TK421') 12 | self.event = EventFactory() 13 | self.transactions = [TransactionFactory(order=self.order)] 14 | self.table = FinanceTable(self.event, self.transactions) 15 | 16 | def test_headers(self): 17 | self.assertEqual(len(self.table.headers()), 8) 18 | 19 | def test_row_count(self): 20 | self.assertEqual(len(list(self.table.get_rows())), 1) 21 | 22 | def test_inclusion_of_header_row(self): 23 | self.assertEqual(len(list(self.table.get_rows(include_headers=True))), 2) 24 | 25 | def test_transaction_created_by_blank(self): 26 | name = self.table.created_by_name(TransactionFactory()) 27 | self.assertEqual('', name) 28 | 29 | def test_transaction_created_by_name(self): 30 | creator = PersonFactory(first_name='Leia', last_name='Organa') 31 | created_transaction = TransactionFactory(created_by=creator) 32 | name = self.table.created_by_name(created_transaction) 33 | self.assertEqual('Leia Organa', name) 34 | 35 | def test_transaction_with_order_code(self): 36 | code = self.table.order_code(self.transactions[0]) 37 | self.assertEqual('TK421', code) 38 | 39 | def test_transaction_without_order(self): 40 | transaction = TransactionFactory(order=None) 41 | code = self.table.order_code(transaction) 42 | self.assertEqual('', code) 43 | 44 | def test_transaction_as_cell_row(self): 45 | row = list(self.table.get_rows())[0] 46 | self.assertEqual(len(row), 8) 47 | self.assertEqual(row[4].field, 'order') 48 | self.assertEqual(row[4].value, 'TK421') 49 | --------------------------------------------------------------------------------