Skip to content

Commit

Permalink
chore: Release v1.10.0 (#6687)
Browse files Browse the repository at this point in the history
chore: Release v1.10.0
  • Loading branch information
iamareebjamal committed Dec 22, 2019
2 parents 36e20e9 + f54d0c4 commit e2341cc
Show file tree
Hide file tree
Showing 33 changed files with 259 additions and 136 deletions.
2 changes: 1 addition & 1 deletion .env.example
Expand Up @@ -2,7 +2,7 @@ DATABASE_URL=postgresql://open_event_user:opev_pass@127.0.0.1:5432/oevent
INTEGRATE_SOCKETIO=false
TEST_DATABASE_URL=postgresql://open_event_user:opev_pass@127.0.0.1:5432/opev_test
APP_CONFIG=config.DevelopmentConfig
ENABLE_ELASTICSEARCH=true
ENABLE_ELASTICSEARCH=false
ELASTICSEARCH_HOST=localhost:9200

POSTGRES_USER=open_event_user
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,15 @@
## Changelog

##### 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.
Run `python manage.py fix_event_and_speaker_images` to resize images which weren't resized due to the bug
- Optimize link generation of relationships with up to 10X speedup
- Add scheduled job to automatically remove orphan ticket holders with no order ID
- Add created and modified times in ticket holder
- Allow new tickets to have same name as deleted tickets
- Fix PayTM payment gateway

##### v1.9.0 (2019-11-28):

- Fix billing info requirements from attendees
Expand Down
15 changes: 4 additions & 11 deletions Dockerfile
@@ -1,5 +1,4 @@
FROM python:3.7.4-alpine as base
LABEL maintainer="Niranjan Rajendran <me@niranjan.io>"
FROM python:3.7-alpine as base

####

Expand All @@ -9,25 +8,19 @@ WORKDIR /install

RUN apk update && \
apk add --virtual build-deps git gcc python3-dev musl-dev jpeg-dev zlib-dev libevent-dev file-dev libffi-dev openssl && \
apk add postgresql-dev && \
pip install setuptools
apk add postgresql-dev

ADD requirements.txt /requirements.txt
ADD requirements /requirements/

RUN wget https://bootstrap.pypa.io/ez_setup.py && python ez_setup.py

ENV PYTHONPATH /install/lib/python3.7/site-packages
RUN pip install --install-option="--prefix=/install" setuptools && \
LIBRARY_PATH=/lib:/usr/lib pip install --install-option="--prefix=/install" -r /requirements.txt
RUN pip install --prefix=/install --no-warn-script-location -r /requirements.txt

####

FROM base

COPY --from=builder /install /usr/local
RUN apk --no-cache add postgresql-dev ca-certificates libxslt jpeg zlib file libxml2 git && \
pip install git+https://github.com/fossasia/flask-rest-jsonapi.git@0.12.6.1#egg=flask-rest-jsonapi
RUN apk --no-cache add postgresql-libs ca-certificates libxslt jpeg zlib file libxml2

WORKDIR /data/app
ADD . .
Expand Down
10 changes: 8 additions & 2 deletions app/__init__.py
Expand Up @@ -18,6 +18,7 @@
from flask_login import current_user
from flask_jwt_extended import JWTManager
from flask_limiter import Limiter
from flask_limiter.util import get_ipaddr
from datetime import timedelta
from flask_cors import CORS
from flask_rest_jsonapi.errors import jsonapi_errors
Expand All @@ -40,7 +41,8 @@
from app.api.helpers.auth import AuthManager, is_token_blacklisted
from app.api.helpers.scheduled_jobs import send_after_event_mail, send_event_fee_notification, \
send_event_fee_notification_followup, change_session_state_on_event_completion, \
expire_pending_tickets, send_monthly_event_invoice, event_invoices_mark_due
expire_pending_tickets, send_monthly_event_invoice, event_invoices_mark_due, \
delete_ticket_holders_no_order_id
from app.models.event import Event
from app.models.role_invite import RoleInvite
from app.views.healthcheck import health_check_celery, health_check_db, health_check_migrations, check_migrations
Expand All @@ -49,14 +51,15 @@
from app.views.redis_store import redis_store
from app.views.celery_ import celery
from app.templates.flask_ext.jinja.filters import init_filters
from app.extensions import shell


BASE_DIR = os.path.dirname(os.path.abspath(__file__))

static_dir = os.path.dirname(os.path.dirname(__file__)) + "/static"
template_dir = os.path.dirname(__file__) + "/templates"
app = Flask(__name__, static_folder=static_dir, template_folder=template_dir)
limiter = Limiter(app)
limiter = Limiter(app, key_func=get_ipaddr)
env.read_envfile()


Expand Down Expand Up @@ -196,6 +199,8 @@ def create_app():
# redis
redis_store.init_app(app)

shell.init_app(app)

# elasticsearch
if app.config['ENABLE_ELASTICSEARCH']:
client.init_app(app)
Expand Down Expand Up @@ -270,6 +275,7 @@ def update_sent_state(sender=None, headers=None, **kwargs):
scheduler.add_job(expire_pending_tickets, 'cron', minute=45)
scheduler.add_job(send_monthly_event_invoice, 'cron', day=1, month='1-12')
scheduler.add_job(event_invoices_mark_due, 'cron', hour=5)
scheduler.add_job(delete_ticket_holders_no_order_id, 'cron', minute=5)
scheduler.start()


Expand Down
3 changes: 1 addition & 2 deletions app/api/auth.py
Expand Up @@ -13,7 +13,6 @@
current_user, create_access_token,
create_refresh_token, set_refresh_cookies,
get_jwt_identity)
from flask_limiter.util import get_remote_address
from healthcheck import EnvironmentDump
from sqlalchemy.orm.exc import NoResultFound

Expand Down Expand Up @@ -289,7 +288,7 @@ def resend_verification_email():
'3/hour', key_func=lambda: request.json['data']['email'], error_message='Limit for this action exceeded'
)
@limiter.limit(
'1/minute', key_func=get_remote_address, error_message='Limit for this action exceeded'
'1/minute', error_message='Limit for this action exceeded'
)
def reset_password_post():
try:
Expand Down
3 changes: 1 addition & 2 deletions app/api/custom/orders.py
@@ -1,6 +1,5 @@
from flask import Blueprint, jsonify, request
from flask_jwt_extended import current_user, jwt_required
from flask_limiter.util import get_remote_address
from sqlalchemy.orm.exc import NoResultFound


Expand Down Expand Up @@ -50,7 +49,7 @@ def ticket_attendee_authorized(order_identifier):
'5/minute', key_func=lambda: request.json['data']['user'], error_message='Limit for this action exceeded'
)
@limiter.limit(
'60/minute', key_func=get_remote_address, error_message='Limit for this action exceeded'
'60/minute', error_message='Limit for this action exceeded'
)
def resend_emails():
"""
Expand Down
4 changes: 2 additions & 2 deletions app/api/events.py
Expand Up @@ -56,8 +56,8 @@ def validate_event(user, modules, data):
if not user.can_create_event():
raise ForbiddenException({'source': ''},
"Please verify your Email")
elif data.get('is_ticketing_enabled', True) and not modules.ticket_include:
raise ForbiddenException({'source': '/data/attributes/is-ticketing-enabled'},
elif not modules.ticket_include:
raise ForbiddenException({'source': ''},
"Ticketing is not enabled in the system")
if data.get('can_pay_by_paypal', False) or data.get('can_pay_by_cheque', False) or \
data.get('can_pay_by_bank', False) or data.get('can_pay_by_stripe', False):
Expand Down
6 changes: 3 additions & 3 deletions app/api/helpers/checksum.py
Expand Up @@ -102,8 +102,8 @@ def __encode__(to_encode, iv, key):
# Pad
to_encode = __pad__(to_encode)
# Encrypt
c = AES.new(key, AES.MODE_CBC, iv)
to_encode = c.encrypt(to_encode)
c = AES.new(key.encode('UTF-8'), AES.MODE_CBC, iv.encode('UTF-8'))
to_encode = c.encrypt(to_encode.encode('UTF-8'))
# Encode
to_encode = base64.b64encode(to_encode)
return to_encode.decode("UTF-8")
Expand All @@ -113,7 +113,7 @@ def __decode__(to_decode, iv, key):
# Decode
to_decode = base64.b64decode(to_decode)
# Decrypt
c = AES.new(key, AES.MODE_CBC, iv)
c = AES.new(key.encode('UTF-8'), AES.MODE_CBC, iv.encode('UTF-8'))
to_decode = c.decrypt(to_decode)
if type(to_decode) == bytes:
# convert bytes array to str.
Expand Down
12 changes: 10 additions & 2 deletions app/api/helpers/files.py
Expand Up @@ -5,6 +5,7 @@
import urllib.parse
import urllib.request
import uuid
import requests

import PIL
from PIL import Image
Expand Down Expand Up @@ -77,7 +78,7 @@ def create_save_resized_image(image_file, basewidth=None, maintain_aspect=None,
if not image_file:
return None
filename = '{filename}.{ext}'.format(filename=get_file_name(), ext=ext)
data = urllib.request.urlopen(image_file).read()
data = requests.get(image_file).content
image_file = io.BytesIO(data)
try:
im = Image.open(image_file)
Expand Down Expand Up @@ -129,7 +130,14 @@ def create_save_image_sizes(image_file, image_sizes_type, unique_identifier=None
try:
image_sizes = ImageSizes.query.filter_by(type=image_sizes_type).one()
except NoResultFound:
image_sizes = ImageSizes(image_sizes_type, 1300, 500, True, 100, 75, 30, True, 100, 500, 200, True, 100)
image_sizes = ImageSizes(image_sizes_type, full_width=1300,
full_height=500, full_aspect=True, full_quality=80,
icon_width=75, icon_height=30, icon_aspect=True,
icon_quality=80, thumbnail_width=500, thumbnail_height=200,
thumbnail_aspect=True, thumbnail_quality=80, logo_width=500,
logo_height=200, icon_size_width_height=35, icon_size_quality=80,
small_size_width_height=50, small_size_quality=80,
thumbnail_size_width_height=500)

# Get an unique identifier from uuid if not provided
if unique_identifier is None:
Expand Down
11 changes: 7 additions & 4 deletions app/api/helpers/mail.py
Expand Up @@ -363,18 +363,21 @@ def send_email_to_attendees(order, purchaser_id, attachments=None):


def send_order_cancel_email(order):
cancel_msg = ''
if order.cancel_note:
cancel_msg = u"<br/>Message from the organizer: {cancel_note}".format(cancel_note=order.cancel_note)

send_email(
to=order.user.email,
action=TICKET_CANCELLED,
subject=MAILS[TICKET_CANCELLED]['subject'].format(
event_name=order.event.name,
invoice_id=order.invoice_number,
frontend_url=get_settings()['frontend_url']
),
html=MAILS[TICKET_CANCELLED]['message'].format(
event_name=order.event.name,
order_url=make_frontend_url('/orders/{identifier}'.format(identifier=order.identifier)),
cancel_note=order.cancel_note,
frontend_url=get_settings()['frontend_url']
frontend_url=get_settings()['frontend_url'],
cancel_msg=cancel_msg,
app_name=get_settings()['app_name']
)
)
11 changes: 11 additions & 0 deletions app/api/helpers/scheduled_jobs.py
Expand Up @@ -21,6 +21,7 @@
from app.models.session import Session
from app.models.ticket import Ticket
from app.models.ticket_fee import TicketFees, get_fee
from app.models.ticket_holder import TicketHolder

from app.settings import get_settings

Expand Down Expand Up @@ -155,6 +156,16 @@ def expire_pending_tickets():
db.session.commit()


def delete_ticket_holders_no_order_id():
from app import current_app as app
with app.app_context():
order_expiry_time = get_settings()['order_expiry_time']
TicketHolder.query.filter(TicketHolder.order_id == None, TicketHolder.deleted_at.is_(None),
TicketHolder.created_at + datetime.timedelta(minutes=order_expiry_time)
< datetime.datetime.utcnow()).delete(synchronize_session=False)
db.session.commit()


def event_invoices_mark_due():
from app import current_app as app
with app.app_context():
Expand Down
12 changes: 7 additions & 5 deletions app/api/helpers/system_mails.py
Expand Up @@ -186,11 +186,13 @@
'recipient': 'User',
'subject': u'Your order for {event_name} has been cancelled ({invoice_id})',
'message': (
u"Hi,Your order for {event_name} has been cancelled has been cancelled by the organizer"
u"<br/>Please contact the organizer for more info" +
u"<br/>Message from the organizer: {cancel_note}"
u"<br/> <a href='{order_url}'>Click here</a> to view/download the invoice."
u"<br/>Login to manage the orders at {frontend_url} </em>"
u"Hello,"
u"<br/>your order for {event_name} has been cancelled by the organizer."
u"<br/>Please contact the organizer for more info." +
u"{cancel_msg}"
u"<br/>To manage orders please login to {frontend_url} and visit \"My Tickets\"."
u"<br/>Best regards,"
u"<br/>{app_name} Team"
)
},
EVENT_EXPORTED: {
Expand Down
12 changes: 6 additions & 6 deletions app/api/helpers/tasks.py
Expand Up @@ -126,7 +126,7 @@ def send_email_task_smtp(payload, smtp_config, headers=None):

@celery.task(base=RequestContextTask, name='resize.event.images', bind=True)
def resize_event_images_task(self, event_id, original_image_url):
event = safe_query(db, Event, 'id', event_id, 'event_id')
event = Event.query.get(event_id)
try:
logging.info('Event image resizing tasks started {}'.format(original_image_url))
uploaded_images = create_save_image_sizes(original_image_url, 'event-image', event.id)
Expand All @@ -135,7 +135,7 @@ def resize_event_images_task(self, event_id, original_image_url):
event.icon_image_url = uploaded_images['icon_image_url']
save_to_db(event)
logging.info('Resized images saved successfully for event with id: {}'.format(event_id))
except (urllib.error.HTTPError, urllib.error.URLError):
except (requests.exceptions.HTTPError, requests.exceptions.InvalidURL):
logging.exception('Error encountered while generating resized images for event with id: {}'.format(event_id))


Expand All @@ -152,7 +152,7 @@ def resize_user_images_task(self, user_id, original_image_url):
user.icon_image_url = uploaded_images['icon_image_url']
save_to_db(user)
logging.info('Resized images saved successfully for user with id: {}'.format(user_id))
except (urllib.error.HTTPError, urllib.error.URLError):
except (requests.exceptions.HTTPError, requests.exceptions.InvalidURL):
logging.exception('Error encountered while generating resized images for user with id: {}'.format(user_id))


Expand All @@ -166,13 +166,13 @@ def sponsor_logos_url_task(self, event_id):
sponsor.logo_url = new_logo_url
save_to_db(sponsor)
logging.info('Sponsor logo url successfully generated')
except(urllib.error.HTTPError, urllib.error.URLError):
except(requests.exceptions.HTTPError, requests.exceptions.InvalidURL):
logging.exception('Error encountered while logo generation')


@celery.task(base=RequestContextTask, name='resize.speaker.images', bind=True)
def resize_speaker_images_task(self, speaker_id, photo_url):
speaker = safe_query(db, Speaker, 'id', speaker_id, 'speaker_id')
speaker = Speaker.query.get(speaker_id)
try:
logging.info('Speaker image resizing tasks started for speaker with id {}'.format(speaker_id))
uploaded_images = create_save_image_sizes(photo_url, 'speaker-image', speaker_id)
Expand All @@ -181,7 +181,7 @@ def resize_speaker_images_task(self, speaker_id, photo_url):
speaker.icon_image_url = uploaded_images['icon_image_url']
save_to_db(speaker)
logging.info('Resized images saved successfully for speaker with id: {}'.format(speaker_id))
except (urllib.error.HTTPError, urllib.error.URLError):
except (requests.exceptions.HTTPError, requests.exceptions.InvalidURL):
logging.exception('Error encountered while generating resized images for event with id: {}'.format(speaker_id))


Expand Down
11 changes: 11 additions & 0 deletions app/api/schema/__init__.py
@@ -0,0 +1,11 @@
# Monkey Patch Marshmallow JSONAPI
from marshmallow_jsonapi.flask import Relationship


def serialize(self, attr, obj, accessor=None):
if self.include_resource_linkage or self.include_data:
return super(Relationship, self).serialize(attr, obj, accessor)
return self._serialize(None, attr, obj)


Relationship.serialize = serialize
7 changes: 0 additions & 7 deletions app/api/schema/events.py
Expand Up @@ -63,23 +63,17 @@ def validate_timezone(self, data, original_data):
owner_name = fields.Str(allow_none=True)
is_map_shown = fields.Bool(default=False)
has_owner_info = fields.Bool(default=False)
has_sessions = fields.Bool(default=0, dump_only=True)
has_speakers = fields.Bool(default=0, dump_only=True)
owner_description = fields.Str(allow_none=True)
is_sessions_speakers_enabled = fields.Bool(default=False)
privacy = fields.Str(default="public")
state = fields.Str(validate=validate.OneOf(choices=["published", "draft"]), allow_none=True, default='draft')
ticket_url = fields.Url(allow_none=True)
code_of_conduct = fields.Str(allow_none=True)
schedule_published_on = fields.DateTime(allow_none=True)
is_ticketing_enabled = fields.Bool(default=False)
is_featured = fields.Bool(default=False)
is_ticket_form_enabled = fields.Bool(default=True)
payment_country = fields.Str(allow_none=True)
payment_currency = fields.Str(allow_none=True)
tickets_available = fields.Float(dump_only=True)
tickets_sold = fields.Float(dump_only=True)
revenue = fields.Float(dump_only=True)
paypal_email = fields.Str(allow_none=True)
is_tax_enabled = fields.Bool(default=False)
is_billing_info_mandatory = fields.Bool(default=False)
Expand All @@ -100,7 +94,6 @@ def validate_timezone(self, data, original_data):
pentabarf_url = fields.Url(dump_only=True)
ical_url = fields.Url(dump_only=True)
xcal_url = fields.Url(dump_only=True)
average_rating = fields.Float(dump_only=True)
refund_policy = fields.String(dump_only=True,
default='All sales are final. No refunds shall be issued in any case.')
is_stripe_linked = fields.Boolean(dump_only=True, allow_none=True, default=False)
Expand Down
2 changes: 1 addition & 1 deletion app/api/schema/orders.py
Expand Up @@ -117,7 +117,7 @@ def initial_values(self, data):
type_="event")

event_invoice = Relationship(attribute='invoice',
self_view='v1.order_invoice',
self_view='v1.order_event_invoice',
self_view_kwargs={'order_identifier': '<identifier>'},
related_view='v1.event_invoice_detail',
related_view_kwargs={'id': '<id>'},
Expand Down
2 changes: 1 addition & 1 deletion app/api/schema/users.py
Expand Up @@ -100,7 +100,7 @@ class Meta:
type_='feedback')
event_invoice = Relationship(
attribute='event_invoice',
self_view='v1.user_event_invoice',
self_view='v1.user_event_invoices',
self_view_kwargs={'id': '<id>'},
related_view='v1.event_invoice_list',
related_view_kwargs={'user_id': '<id>'},
Expand Down

0 comments on commit e2341cc

Please sign in to comment.