Skip to content

Commit

Permalink
Unique coupon code in org and assignee email in item (#295)
Browse files Browse the repository at this point in the history
* checking for unique coupon code in org

* forgot to add participant view

* linting fix

* added makefile for easy build

* added csrf token in form

* added link to github issue and 404

* fixed timezone in query

* fixed item collection description and error message for assignee details

* removed field specific errors

* Move webpack config and package json to assets folder. Add Make file. (#296)

* Move webpack config and package json to assets folder. Add Make file

* remove manifest.json

* node_modules is already ignored below

* Add make to travis script file

* removed extra fields from AssigneeForm

* fix form save

* added todo for sending notification on asignee update
  • Loading branch information
Bibhas authored Aug 1, 2019
1 parent e7107b4 commit a97af60
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 52 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ instance/testing.py
.webassets-cache
baseframe-packed.js
baseframe-packed.css
boxoffice/static/js/npm-debug.log
.vscode
.coverage
boxoffice/static/gen/*
Expand Down
19 changes: 13 additions & 6 deletions boxoffice/assets/js/views/order.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export const Order = {

formValidator.setMessage('required', 'Please fill out the %s field');
formValidator.setMessage('valid_email', 'Please enter a valid email');

formValidator.registerCallback('validate_phone', function(phone) {
//Remove all punctations (except +) and letters
phone = phone.replace(/[^0-9+]/g,'');
Expand Down Expand Up @@ -156,11 +156,12 @@ export const Order = {
contentType: 'application/json',
data: JSON.stringify({
attendee: attendeeDetails,
line_item_id: line_item_id
line_item_id: line_item_id,
csrf_token: $('meta[name=csrf-token]').attr('content')
}),
timeout: 5000,
timeout: 30000,
retries: 5,
retryInterval: 5000,
retryInterval: 30000,
success: function(data) {
order.ticketComponent.set(line_item + '.assigningTicket', false);
order.ticketComponent.set(line_item + '.toAssign', false);
Expand All @@ -169,8 +170,14 @@ export const Order = {
},
error: function(response) {
var ajaxLoad = this;
var onServerError = function() {
order.ticketComponent.set(line_item + '.errorMsg', 'Server error');
var onServerError = function () {
console.log(response)
if (response.responseJSON !== undefined && response.responseJSON.error_description !== undefined) {
var errorMsg = response.responseJSON.error_description;
} else {
var errorMsg = "Server error.";
}
order.ticketComponent.set(line_item + '.errorMsg', errorMsg);
order.ticketComponent.set(line_item + '.assigningTicket', false);
};
var onNetworkError = function() {
Expand Down
4 changes: 2 additions & 2 deletions boxoffice/assets/js/views/org.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const orgTemplate = `
<p class="section-title">Item collection id</p>
<p class="section-content">{{ id }}</p>
<p class="section-title">Item collection description</p>
<div class="section-content">{{{ description }}}</div>
<div class="section-content">{{{ description.html }}}</div>
<div class="btn-wrapper">
<a class="boxoffice-button boxoffice-button-info" href="/{{ orgName }}/{{ name }}">View listing</a>
<a class="boxoffice-button boxoffice-button-primary" href="/admin/ic/{{id}}" data-navigate>View dashboard</a>
Expand All @@ -48,7 +48,7 @@ export const OrgView = {
render: function({org_name}={}) {
fetch({
url: urlFor('view', {resource: 'o', id: org_name, root: true})
}).then(function({id, org_title, item_collections, form}) {
}).then(function ({ id, org_title, item_collections, form }) {
let orgComponent = new Ractive({
el: '#main-content-area',
template: orgTemplate,
Expand Down
1 change: 1 addition & 0 deletions boxoffice/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
from .order_refund import *
from .discount import *
from .category import *
from .participant import *
31 changes: 25 additions & 6 deletions boxoffice/forms/discount.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
# -*- coding: utf-8 -*-

from baseframe import __
import baseframe.forms as forms
from baseframe.forms.sqlalchemy import QuerySelectMultipleField, QuerySelectField
from coaster.utils import getbool
from baseframe import __
from baseframe.forms.sqlalchemy import (
AvailableAttr,
QuerySelectField,
QuerySelectMultipleField,
)
from baseframe.forms.validators import StopValidation
from baseframe.forms.sqlalchemy import AvailableAttr
from ..models import DISCOUNT_TYPE, CURRENCY, Item, ItemCollection, db, DiscountCoupon
from coaster.utils import getbool

from ..models import (
CURRENCY,
DISCOUNT_TYPE,
DiscountCoupon,
DiscountPolicy,
Item,
ItemCollection,
db,
)

__all__ = ['DiscountPolicyForm', 'PriceBasedDiscountPolicyForm', 'DiscountPriceForm', 'DiscountCouponForm', 'AutomaticDiscountPolicyForm', 'CouponBasedDiscountPolicyForm']

Expand Down Expand Up @@ -84,7 +96,14 @@ def set_queries(self):


def validate_unique_discount_coupon_code(form, field):
if DiscountCoupon.query.filter(DiscountCoupon.discount_policy == form.edit_parent, DiscountCoupon.code == field.data).notempty():
if (
DiscountCoupon.query.join(DiscountPolicy)
.filter(
DiscountPolicy.organization == form.edit_parent.organization,
DiscountCoupon.code == field.data
)
.notempty()
):
raise StopValidation(__("This discount coupon code already exists. Please enter a different coupon code"))


Expand Down
34 changes: 34 additions & 0 deletions boxoffice/forms/participant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-

from baseframe import __
import baseframe.forms as forms

from boxoffice.models import Assignee, Item, LineItem

__all__ = ['AssigneeForm']


class AssigneeForm(forms.Form):
email = forms.EmailField(
__("Email"),
validators=[forms.validators.DataRequired(), forms.validators.Length(max=254)],
)
fullname = forms.StringField(
__("Full Name"), validators=[forms.validators.DataRequired()]
)
phone = forms.StringField(
__("Phone number"), validators=[forms.validators.DataRequired()]
)

def validate_email(self, field):
existing_assignees = (
Assignee.query.join(LineItem)
.filter(LineItem.item == self.edit_parent.item)
.filter(Assignee.email == field.data)
)
if self.edit_obj is not None:
existing_assignees = existing_assignees.filter(
Assignee.id != self.edit_obj.id
)
if existing_assignees.count() > 0:
raise forms.ValidationError(__("Email address has been already used"))
15 changes: 13 additions & 2 deletions boxoffice/models/discount_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from werkzeug import cached_property
from itsdangerous import Signer, BadSignature
from sqlalchemy import event, DDL
from sqlalchemy.orm.exc import MultipleResultsFound
from baseframe import __
from coaster.utils import LabeledEnum, uuid1mc, buid
from coaster.sqlalchemy import cached
Expand Down Expand Up @@ -179,8 +180,18 @@ def is_valid_access_coupon(cls, item, code_list):
if policy and not DiscountCoupon.is_signed_code_usable(policy, code):
break
else:
policy = cls.query.join(DiscountCoupon).filter(
DiscountCoupon.code == code, DiscountCoupon.used_count < DiscountCoupon.usage_limit).one_or_none()
try:
policy = (
cls.query.join(DiscountCoupon)
.filter(
DiscountCoupon.code == code,
DiscountCoupon.used_count < DiscountCoupon.usage_limit,
)
.one_or_none()
)
except MultipleResultsFound as e:
# ref: https://github.com/hasgeek/boxoffice/issues/290
policy = None
if bool(policy) and policy in item.discount_policies:
return True
return False
Expand Down
8 changes: 8 additions & 0 deletions boxoffice/static/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
all: assets

assets:
npm install
npm run build

buildonly:
npm run build
114 changes: 79 additions & 35 deletions boxoffice/views/participant.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,93 @@
# -*- coding: utf-8 -*-

from flask import request, jsonify, make_response
from .. import app
from ..models import db
from ..models import LineItem, Assignee
from ..models import Order
from coaster.views import load_models
from flask import jsonify, make_response, request

from boxoffice.mailclient import send_ticket_assignment_mail
from utils import xhr_only

from coaster.views import load_models, render_with

from .. import app
from ..forms import AssigneeForm
from ..models import Assignee, LineItem, Order, db


@app.route('/participant/<access_token>/assign', methods=['GET', 'OPTIONS', 'POST'])
@xhr_only
@load_models(
(Order, {'access_token': 'access_token'}, 'order')
)
@render_with(json=True)
@load_models((Order, {'access_token': 'access_token'}, 'order'))
def assign(order):
"""
Assign a line_item to a participant
"""
assignee_dict = request.json.get('attendee')
if not request.json or not assignee_dict or not assignee_dict.get('email') or not assignee_dict.get('fullname'):
return make_response(jsonify(status='error', error='missing_attendee_details', error_description="Attendee details are missing"), 400)
line_item = LineItem.query.get(request.json.get('line_item_id'))
if line_item.is_cancelled:
return make_response(jsonify(status='error', error='cancelled_ticket', error_description="Ticket has been cancelled"), 400)

item_assignee_details = line_item.item.assignee_details
assignee_details = {}
if item_assignee_details:
for key in item_assignee_details.keys():
assignee_details[key] = assignee_dict.get(key)
if line_item.current_assignee and assignee_dict['email'] == line_item.current_assignee.email:
# updating details of the current assignee
line_item.current_assignee.fullname = assignee_dict['fullname']
line_item.current_assignee.phone = assignee_dict['phone']
line_item.current_assignee.details = assignee_details
db.session.commit()
if line_item is None:
return (
{
'status': 'error',
'error': 'missing_line_item',
'error_description': u"Invalid line item",
},
404,
)
elif line_item.is_cancelled:
return (
{
'status': 'error',
'error': 'cancelled_ticket',
'error_description': u"Ticket has been cancelled",
},
400,
)

assignee_dict = request.json.get('attendee')
assignee_form = AssigneeForm.from_json(
assignee_dict,
obj=line_item.current_assignee,
parent=line_item,
csrf_token=request.json.get('csrf_token'),
)

if assignee_form.validate_on_submit():
item_assignee_details = line_item.item.assignee_details
assignee_details = {}
if item_assignee_details:
for key in item_assignee_details.keys():
assignee_details[key] = assignee_dict.get(key)
if (
line_item.current_assignee
and assignee_dict['email'] == line_item.current_assignee.email
):
# updating details of the current assignee
line_item.current_assignee.fullname = assignee_dict['fullname']
line_item.current_assignee.phone = assignee_dict['phone']
line_item.current_assignee.details = assignee_details
db.session.commit()
else:
if line_item.current_assignee:
# Assignee is being changed. Archive current assignee.
# TODO: Send notification to previous assignee.
# https://github.com/hasgeek/boxoffice/issues/244
line_item.current_assignee.current = None
new_assignee = Assignee(
current=True,
email=assignee_dict.get('email'),
fullname=assignee_dict.get('fullname'),
phone=assignee_dict.get('phone'),
details=assignee_details,
line_item=line_item,
)
db.session.add(new_assignee)
db.session.commit()
send_ticket_assignment_mail.queue(line_item.id)
return {'status': 'ok', 'result': {'message': 'Ticket assigned'}}
else:
if line_item.current_assignee:
# Archive current assignee
line_item.current_assignee.current = None
new_assignee = Assignee(current=True, email=assignee_dict.get('email'), fullname=assignee_dict.get('fullname'),
phone=assignee_dict.get('phone'), details=assignee_details, line_item=line_item)
db.session.add(new_assignee)
db.session.commit()
send_ticket_assignment_mail.queue(line_item.id)
return make_response(jsonify(status='ok', result={'message': 'Ticket assigned'}), 201)
return (
{
'status': 'error',
'error': 'invalid_assignee_details',
'error_description': u"Invalid form values. Please resubmit.",
'error_details': assignee_form.errors,
},
400,
)

0 comments on commit a97af60

Please sign in to comment.