Skip to content

Commit

Permalink
chore : Release v1.11 (#6768)
Browse files Browse the repository at this point in the history
chore : Release v1.11

Co-authored-by: Areeb Jamal <[email protected]>
Co-authored-by: Suneet Srivastava <[email protected]>
Co-authored-by: null <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Prateek Jain <[email protected]>
Co-authored-by: Amit Aronovitch <[email protected]>
Co-authored-by: D.Rohit <[email protected]>
Co-authored-by: Ritik Jain <[email protected]>
Co-authored-by: Nitin Kumar <[email protected]>
Co-authored-by: Sai Charan <[email protected]>
  • Loading branch information
9 people authored Jan 22, 2020
2 parents e2341cc + f83ef03 commit cc5752d
Show file tree
Hide file tree
Showing 122 changed files with 1,417 additions and 1,182 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ DATABASE_URL=postgresql://open_event_user:[email protected]:5432/oevent
INTEGRATE_SOCKETIO=false
TEST_DATABASE_URL=postgresql://open_event_user:[email protected]:5432/opev_test
APP_CONFIG=config.DevelopmentConfig
ENABLE_ELASTICSEARCH=false
ELASTICSEARCH_HOST=localhost:9200

POSTGRES_USER=open_event_user
POSTGRES_PASSWORD=opev_pass
POSTGRES_DB=open_event

FLASK_APP=app.instance
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Fixes #

- [ ] I have read the [Contribution & Best practices Guide](https://blog.fossasia.org/open-source-developer-guide-and-best-practices-at-fossasia) and my PR follows them.
- [ ] My branch is up-to-date with the Upstream `development` branch.
- [ ] The unit tests pass locally with my changes <!-- use `nosetests tests/all` to run all the tests -->
- [ ] The unit tests pass locally with my changes <!-- use `nosetests tests/` to run all the tests -->
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] I have added necessary documentation (if appropriate)
<!-- If an existing function does not have a docstring, please add one -->
Expand Down
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ install:
- pip3 install -r requirements/tests.txt

env:
- APP_CONFIG="config.TestingConfig" PATH=$PATH:${HOME}/google-cloud-sdk/bin
- APP_CONFIG="config.TestingConfig" SECRET_KEY="super secret key" PATH=$PATH:${HOME}/google-cloud-sdk/bin

before_script:
- psql -c 'create database test;' -U postgres
Expand All @@ -30,7 +30,7 @@ before_script:
- bash scripts/test_multiple_heads.sh

script:
- nosetests tests/all -v --with-coverage
- nosetests tests/ -v --with-coverage

after_success:
- 'bash <(curl -s https://codecov.io/bash)'
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
## Changelog

#### v1.11.0 (2020-01-23):

- **BREAKING:** Fix security issues related to secret key. You **MUST** add the current secret key set in DB as `SECRET_KEY` environment variable before upgrading. After upgrading, the column will be removed from DB
- Fix count query of tickets used to reserve tickets
- Support event statistics in include query
- Restrict deletion of orders except by admin
- Fix missing fields and incorrect column value in session csv export
- Addition of field for Promoted Events, Instagram Speaker URL, Age Groups for Attendee
- Replaced jobs running with APS to celery-beat
- Fix sessions can be edited after CFS has ended
- Removed elasticsearch initialisation and redundant APIs
- Change country field type to select in forms
- Other minor fixes

##### v1.10.0 (2019-12-22):

- Fix event and speaker image resizing, and add management command to resize event and speaker images which remained to be resized.
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ WORKDIR /data/app
ADD . .

EXPOSE 8080
CMD ["sh", "scripts/container_start.sh"]
ENTRYPOINT ["sh", "scripts/container_start.sh"]
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,13 @@ export APP_CONFIG=config.TestingConfig

* Then go to the project directory and run the following command:
```
python3 -m unittest discover tests/all/
python3 -m unittest discover tests/
```
* It will run each test one by one.

* You can also use the following command to run tests using nosetests:
```
nosetests tests/all/
nosetests tests/
```

#### Running robot framework tests
Expand Down
3 changes: 3 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"APP_SECRET_TOKEN": {
"generator": "secret"
},
"SECRET_KEY": {
"generator": "secret"
},
"ON_HEROKU": "true",
"FORCE_SSL": "true",
"INVITATION_CODE": {
Expand Down
59 changes: 20 additions & 39 deletions app/api/attendees.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
from datetime import datetime
import datetime

from flask import Blueprint, request, jsonify, abort, make_response
from flask_jwt_extended import current_user
from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
from flask_rest_jsonapi.exceptions import ObjectNotFound
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy import or_, and_

from app.api.bootstrap import api
from app.api.helpers.db import safe_query, get_count
Expand All @@ -13,7 +11,6 @@
ForbiddenException,
UnprocessableEntity,
)
from app.api.helpers.mail import send_email_to_attendees
from app.api.helpers.permission_manager import has_access
from app.api.helpers.permissions import jwt_required
from app.api.helpers.query import event_query
Expand All @@ -25,7 +22,23 @@
from app.models.ticket_holder import TicketHolder
from app.models.user import User

attendee_misc_routes = Blueprint('attendee_misc', __name__, url_prefix='/v1')
from app.settings import get_settings


def get_sold_and_reserved_tickets_count(event_id):
order_expiry_time = get_settings()['order_expiry_time']
return db.session.query(TicketHolder.id).join(Order).filter(TicketHolder.order_id == Order.id) \
.filter(Order.event_id == int(event_id),
Order.deleted_at.is_(None),
or_(Order.status == 'placed',
Order.status == 'completed',
and_(Order.status == 'initializing',
Order.created_at + datetime.timedelta(
minutes=order_expiry_time) > datetime.datetime.utcnow()),
and_(Order.status == 'pending',
Order.created_at + datetime.timedelta(
minutes=30 + order_expiry_time) > (datetime.datetime.utcnow()))
)).count()


class AttendeeListPost(ResourceList):
Expand Down Expand Up @@ -56,8 +69,7 @@ def before_post(self, args, kwargs, data):
"Ticket belongs to a different Event"
)
# Check if the ticket is already sold out or not.
if get_count(db.session.query(TicketHolder.id).
filter_by(ticket_id=int(data['ticket']), deleted_at=None)) >= ticket.quantity:
if get_sold_and_reserved_tickets_count(ticket.event_id) >= ticket.quantity:
raise ConflictException(
{'pointer': '/data/attributes/ticket_id'},
"Ticket already sold out"
Expand Down Expand Up @@ -257,34 +269,3 @@ class AttendeeRelationshipOptional(ResourceRelationship):
schema = AttendeeSchema
data_layer = {'session': db.session,
'model': TicketHolder}


@attendee_misc_routes.route('/attendees/send-receipt', methods=['POST'])
@jwt_required
def send_receipt():
"""
Send receipts to attendees related to the provided order.
:return:
"""
order_identifier = request.json.get('order-identifier')
if order_identifier:
try:
order = db.session.query(Order).filter_by(identifier=order_identifier).one()
except NoResultFound:
raise ObjectNotFound({'parameter': '{identifier}'}, "Order not found")

if (order.user_id != current_user.id) and (not has_access('is_registrar', event_id=order.event_id)):
abort(
make_response(jsonify(error="You need to be the event organizer or order buyer to send receipts."), 403)
)
elif order.status != 'completed':
abort(
make_response(jsonify(error="Cannot send receipt for an incomplete order"), 409)
)
else:
send_email_to_attendees(order, current_user.id)
return jsonify(message="receipt sent to attendees")
else:
abort(
make_response(jsonify(error="Order identifier missing"), 422)
)
4 changes: 2 additions & 2 deletions app/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
from healthcheck import EnvironmentDump
from sqlalchemy.orm.exc import NoResultFound

from app import get_settings
from app import limiter
from app.api.helpers.db import save_to_db, get_count
from app.api.helpers.auth import AuthManager, blacklist_token
from app.api.helpers.jwt import jwt_authenticate
Expand All @@ -28,11 +26,13 @@
from app.api.helpers.notification import send_notification_with_action
from app.api.helpers.third_party_auth import GoogleOAuth, FbOAuth, TwitterOAuth, InstagramOAuth
from app.api.helpers.utilities import get_serializer, str_generator
from app.extensions.limiter import limiter
from app.models import db
from app.models.mail import PASSWORD_RESET, PASSWORD_CHANGE, \
PASSWORD_RESET_AND_VERIFY
from app.models.notification import PASSWORD_CHANGE as PASSWORD_CHANGE_NOTIF
from app.models.user import User
from app.settings import get_settings


logger = logging.getLogger(__name__)
Expand Down
45 changes: 45 additions & 0 deletions app/api/custom/attendees.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from flask import Blueprint, request, jsonify, abort, make_response
from flask_jwt_extended import current_user
from sqlalchemy.orm.exc import NoResultFound

from app.api.helpers.mail import send_email_to_attendees
from app.api.helpers.permissions import jwt_required
from app.api.helpers.errors import (
UnprocessableEntityError,
NotFoundError,
ForbiddenError,
)
from app.api.helpers.permission_manager import has_access

from app.models.order import Order
from app.models import db

attendee_blueprint = Blueprint('attendee_blueprint', __name__, url_prefix='/v1')


@attendee_blueprint.route('/attendees/send-receipt', methods=['POST'])
@jwt_required
def send_receipt():
"""
Send receipts to attendees related to the provided order.
:return:
"""
order_identifier = request.json.get('order-identifier')
if order_identifier:
try:
order = db.session.query(Order).filter_by(identifier=order_identifier).one()
except NoResultFound:
return NotFoundError({'parameter': '{order_identifier}'}, "Order not found").respond()

if (order.user_id != current_user.id) and (not has_access('is_registrar', event_id=order.event_id)):
return ForbiddenError({'source': ''},
'You need to be the event organizer or order buyer to send receipts.').respond()
elif order.status != 'completed':
abort(
make_response(jsonify(error="Cannot send receipt for an incomplete order"), 409)
)
else:
send_email_to_attendees(order, current_user.id)
return jsonify(message="receipt sent to attendees")
else:
return UnprocessableEntityError({'source': ''}, 'Order identifier missing').respond()
94 changes: 82 additions & 12 deletions app/api/custom/orders.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
from flask import Blueprint, jsonify, request
from flask import Blueprint, jsonify, request, make_response
from flask_jwt_extended import current_user, jwt_required
from sqlalchemy.orm.exc import NoResultFound


from app import limiter
from app.models import db
from app.api.auth import return_file
from app.api.helpers.db import safe_query
from app.api.helpers.db import safe_query, get_count
from app.api.helpers.mail import send_email_to_attendees
from app.api.helpers.errors import ForbiddenError, UnprocessableEntityError, NotFoundError
from app.api.helpers.order import calculate_order_amount, create_pdf_tickets_for_holder
from app.api.helpers.storage import UPLOAD_PATHS
from app.api.helpers.storage import generate_hash
from app.api.helpers.ticketing import TicketingManager
from app.api.schema.attendees import AttendeeSchema
from app.api.schema.orders import OrderSchema
from app.api.helpers.permission_manager import has_access
from app.extensions.limiter import limiter
from app.models.discount_code import DiscountCode
from app.models.order import Order
from app.models.order import Order, OrderTicket
from app.models.ticket import Ticket
from app.models.ticket_holder import TicketHolder

order_blueprint = Blueprint('order_blueprint', __name__, url_prefix='/v1/orders')
ticket_blueprint = Blueprint('ticket_blueprint', __name__, url_prefix='/v1/tickets')
Expand Down Expand Up @@ -79,16 +81,84 @@ def resend_emails():
return ForbiddenError({'source': ''}, "Co-Organizer Access Required").respond()


@order_blueprint.route('/calculate-amount', methods=['POST'])
@jwt_required
def calculate_amount():
data = request.get_json()
def calculate_order_amount_wrapper(data):
tickets = data['tickets']
discount_code = None
if 'discount-code' in data:
discount_code_id = data['discount-code']
discount_code = safe_query(db, DiscountCode, 'id', discount_code_id, 'id')
if not TicketingManager.match_discount_quantity(discount_code, tickets, None):
return UnprocessableEntityError({'source': 'discount-code'}, 'Discount Usage Exceeded').respond()
return tickets, discount_code


@order_blueprint.route('/calculate-amount', methods=['POST'])
@jwt_required
def calculate_amount():
data = request.get_json()
tickets, discount_code = calculate_order_amount_wrapper(data)
return jsonify(calculate_order_amount(tickets, discount_code))


@order_blueprint.route('/create-order', methods=['POST'])
@jwt_required
def create_order():
data = request.get_json()
tickets, discount_code = calculate_order_amount_wrapper(data)
attendee = data['attendee']
for attribute in attendee:
attendee[attribute.replace('-', '_')] = attendee.pop(attribute)
schema = AttendeeSchema()
json_api_attendee = {"data": {"attributes": data['attendee'], "type": "attendee"}}
result = schema.load(json_api_attendee)
if result.errors:
return make_response(jsonify(result.errors), 422)
ticket_ids = {int(ticket['id']) for ticket in tickets}
quantity = {int(ticket['id']): ticket['quantity'] for ticket in tickets}
ticket_list = db.session.query(Ticket).filter(Ticket.id.in_(ticket_ids)).filter_by(event_id=data['event_id'],
deleted_at=None).all()
ticket_ids_found = {ticket_information.id for ticket_information in ticket_list}
tickets_not_found = ticket_ids - ticket_ids_found
if tickets_not_found:
return make_response(jsonify(status='Order Unsuccessful', error='Tickets with id {} were not found in Event {}.'
.format(tickets_not_found, data['event_id'])), 404)
for ticket_info in ticket_list:
if (ticket_info.quantity - get_count(db.session.query(TicketHolder.id).filter_by(
ticket_id=int(ticket_info.id), deleted_at=None))) < quantity[ticket_info.id]:
return make_response(jsonify(status='Order Unsuccessful', error='Ticket already sold out.'), 409)
attendee_list = []
for ticket in tickets:
for ticket_amount in range(ticket['quantity']):
attendee = TicketHolder(**result[0], event_id=int(data['event_id']), ticket_id=int(ticket['id']))
db.session.add(attendee)
attendee_list.append(attendee)
ticket_pricing = calculate_order_amount(tickets, discount_code)
if not has_access('is_coorganizer', event_id=data['event_id']):
data['status'] = 'initializing'
# create on site attendees
# check if order already exists for this attendee.
# check for free tickets and verified user
order = Order(amount=ticket_pricing['total_amount'], user_id=current_user.id, event_id=int(data['event_id']),
status=data['status'])
db.session.add(order)
db.session.commit()
db.session.refresh(order)
order_tickets = {}
for holder in attendee_list:
holder.order_id = order.id
db.session.add(holder)
if not order_tickets.get(holder.ticket_id):
order_tickets[holder.ticket_id] = 1
else:
order_tickets[holder.ticket_id] += 1

create_pdf_tickets_for_holder(order)

for ticket in order_tickets:
od = OrderTicket(order_id=order.id, ticket_id=ticket, quantity=order_tickets[ticket])
db.session.add(od)

order.quantity = order.tickets_count
db.session.add(order)
db.session.commit()
db.session.refresh(order)
order_schema = OrderSchema()
return order_schema.dump(order)
Loading

0 comments on commit cc5752d

Please sign in to comment.