Skip to content

Commit

Permalink
Merge branch 'main' into upgrade-local-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsuta committed Jul 6, 2023
2 parents 072dda7 + 744718e commit 7e0fa64
Show file tree
Hide file tree
Showing 34 changed files with 342 additions and 209 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ MAINTAINER RAMS Project "[email protected]"
LABEL version.rams-core ="0.1"

# install ghostscript and gettext-base
RUN apt-get update && apt-get install -y ghostscript gettext-base vim && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y ghostscript dnsutils gettext-base vim && rm -rf /var/lib/apt/lists/*

ADD requirements*.txt plugins/uber/
ADD setup.py plugins/uber/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Adding last_send_time to automated emails
Revision ID: c31a2e5b6fbd
Revises: fa2deb095760
Create Date: 2022-12-28 07:18:23.125853
"""


# revision identifiers, used by Alembic.
revision = 'c31a2e5b6fbd'
down_revision = 'fa2deb095760'
branch_labels = None
depends_on = None

from alembic import op
import sqlalchemy as sa
import residue



try:
is_sqlite = op.get_context().dialect.name == 'sqlite'
except Exception:
is_sqlite = False

if is_sqlite:
op.get_context().connection.execute('PRAGMA foreign_keys=ON;')
utcnow_server_default = "(datetime('now', 'utc'))"
else:
utcnow_server_default = "timezone('utc', current_timestamp)"

def sqlite_column_reflect_listener(inspector, table, column_info):
"""Adds parenthesis around SQLite datetime defaults for utcnow."""
if column_info['default'] == "datetime('now', 'utc')":
column_info['default'] = utcnow_server_default

sqlite_reflect_kwargs = {
'listeners': [('column_reflect', sqlite_column_reflect_listener)]
}

# ===========================================================================
# HOWTO: Handle alter statements in SQLite
#
# def upgrade():
# if is_sqlite:
# with op.batch_alter_table('table_name', reflect_kwargs=sqlite_reflect_kwargs) as batch_op:
# batch_op.alter_column('column_name', type_=sa.Unicode(), server_default='', nullable=False)
# else:
# op.alter_column('table_name', 'column_name', type_=sa.Unicode(), server_default='', nullable=False)
#
# ===========================================================================


def upgrade():
op.add_column('automated_email', sa.Column('last_send_time', residue.UTCDateTime(), nullable=True))


def downgrade():
op.drop_column('automated_email', 'last_send_time')
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cherrypy==17.3.0
celery==4.1.1
celery==5.1.2
python-dateutil==2.6.0
psycopg2
py3k-bcrypt==0.3
Expand Down
30 changes: 29 additions & 1 deletion uber-wrapper.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
#!/bin/bash
set -e

# SESSION_HOST and BROKER_HOST may point to SRV records that need to get resolved to a host/port
SESSION_REC=$(dig srv +noall +answer +short "$SESSION_HOST" | cut -d ' ' -f 3,4 | head -1)
BROKER_REC=$(dig srv +noall +answer +short "$BROKER_HOST" | cut -d ' ' -f 3,4 | head -1)
if [[ ! -z "$SESSION_REC" ]]; then
SESSION_HOST=$(echo $SESSION_REC | cut -d ' ' -f 2)
SESSION_PORT=$(echo $SESSION_REC | cut -d ' ' -f 1)
fi
if [[ ! -z "$BROKER_REC" ]]; then
BROKER_HOST=$(echo $BROKER_REC | cut -d ' ' -f 2)
BROKER_PORT=$(echo $BROKER_REC | cut -d ' ' -f 1)
fi

# This will replace any variable references in these files
# If you want to add any additional settings here just add
# the variables to the environment when running this.
envsubst < "uber-development.ini.template" > /app/plugins/uber/development.ini
envsubst < "sideboard-development.ini.template" > /app/development.ini

if [ -n "${UBERSYSTEM_GIT_CONFIG}" ]; then
echo "Loading UBERSYSTEM_CONFIG from git repo ${UBERSYSTEM_GIT_CONFIG}"
/app/env/bin/python /app/plugins/uber/make_config.py --repo "${UBERSYSTEM_GIT_CONFIG}" --paths ${UBERSYSTEM_GIT_CONFIG_PATHS}
fi

if [ -n "${UBERSYSTEM_CONFIG}" ]; then
echo "Parsing config from environment"
/app/env/bin/python /app/plugins/uber/make_config.py
fi

if [ "$1" = 'uber' ]; then
echo "If this is the first time starting this server go to the following URL to create an account:"
echo "http://${HOSTNAME}:${PORT}${DEFAULT_URL}/accounts/insert_test_admin"
Expand All @@ -13,4 +41,4 @@ elif [ "$1" = 'celery-worker' ]; then
/app/env/bin/celery -A uber.tasks worker
fi

exec "$@"
exec "$@"
13 changes: 6 additions & 7 deletions uber/automated_emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,19 +126,18 @@ def __init__(
before = [d.active_before for d in when if d.active_before]
self.active_before = max(before) if before else None

@property
def body(self):
return decorators.render_empty(os.path.join('emails', self.template))

@property
def template_url(self):
env = JinjaEnv.env()
try:
template_path = pathlib.Path(env.get_template(os.path.join('emails', self.template)).name)
self.template_plugin = template_path.parts[3]
self.template_url = f"https://github.com/magfest/{self.template_plugin}/tree/main/{self.template_plugin}/{pathlib.Path(*template_path.parts[5:]).as_posix()}"
except jinja2.exceptions.TemplateNotFound:
self.template_url = ""


@property
def body(self):
return decorators.render_empty(os.path.join('emails', self.template))


# Payment reminder emails, including ones for groups, which are always safe to be here, since they just
Expand Down Expand Up @@ -561,7 +560,7 @@ def __init__(self, subject, template, filter, ident, **kwargs):
'Last Chance to Accept Your {EVENT_NAME} ({EVENT_DATE}) Badge',
'placeholders/reminder.txt',
lambda a: a.placeholder and not a.is_dealer,
when=days_before(7, c.PLACEHOLDER_DEADLINE),
when=days_before(7, c.PLACEHOLDER_DEADLINE if c.PLACEHOLDER_DEADLINE else c.UBER_TAKEDOWN),
ident='badge_confirmation_reminder_last_chance')


Expand Down
4 changes: 2 additions & 2 deletions uber/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ def BADGES_SOLD(self):
Attendee.paid == self.PAID_BY_GROUP,
Group.amount_paid > 0).count()

promo_code_badges = session.query(PromoCode).join(PromoCodeGroup).count()
promo_code_badges = session.query(PromoCode).join(PromoCodeGroup).filter(PromoCode.cost > 0).count()

return individuals + group_badges + promo_code_badges

Expand Down Expand Up @@ -1108,7 +1108,7 @@ def _unrepr(d):

c.SETUP_JOB_START = c.EPOCH - timedelta(days=c.SETUP_SHIFT_DAYS)
c.TEARDOWN_JOB_END = c.ESCHATON + timedelta(days=1, hours=23) # Allow two full days for teardown shifts
c.CON_TOTAL_LENGTH = int((c.TEARDOWN_JOB_END - c.SETUP_JOB_START).seconds / 3600)
c.CON_TOTAL_DAYS = -(-(int((c.TEARDOWN_JOB_END - c.SETUP_JOB_START).total_seconds() // 3600)) // 24)
c.PANEL_STRICT_LENGTH_OPTS = [opt for opt in c.PANEL_LENGTH_OPTS if opt != c.OTHER]

c.EVENT_YEAR = c.EPOCH.strftime('%Y')
Expand Down
10 changes: 5 additions & 5 deletions uber/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ def apply(self, params, *, bools=(), checkgroups=(), restricted=True, ignore_csr

def timespan(self, minute_increment=1):
def minutestr(dt):
return ':30' if dt.minute == 30 else ''
return '' if dt.minute == 0 else dt.strftime(':%M')

timespan = timedelta(minutes=minute_increment * self.duration)
endtime = self.start_time_local + timespan
Expand Down Expand Up @@ -990,6 +990,10 @@ def preprocess_refund(self, txn, amount=0, already_refunded_error=True):

if not txn.intent_id:
return "Can't refund a transaction that is not a Stripe payment."

error = txn.check_stripe_id()
if error:
return "Error issuing refund: " + str(error)

if not txn.charge_id:
charge_id = txn.check_paid_from_stripe()
Expand Down Expand Up @@ -1017,10 +1021,6 @@ def process_refund(self, txn, amount=0):

refund_amount = amount or txn.amount_left

balance = stripe.Balance.retrieve()
if balance['available'][0]['amount'] < refund_amount:
return "We cannot currently refund this transaction. Please try again in a few days or contact an administrator."

log.debug('REFUND: attempting to refund Stripe transaction with ID {} {} cents for {}',
txn.stripe_id, refund_amount, txn.desc)

Expand Down
54 changes: 51 additions & 3 deletions uber/models/commerce.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,29 @@ def total_str(self):
"They" if self.current_receipt_amount >= 0 else "We",
format_currency(self.current_receipt_amount / 100))

@property
def last_incomplete_txn(self):
return sorted(self.pending_txns, key=lambda t: t.added, reverse=True)[0] if self.pending_txns else None
def get_last_incomplete_txn(self):
from uber.models import Session

for txn in sorted(self.pending_txns, key=lambda t: t.added, reverse=True):
error = txn.check_stripe_id()
if error or txn.amount != self.current_receipt_amount:
if error:
txn.cancelled = datetime.now() # TODO: Add logs to txns/items and log the automatic cancellation reason?
if txn.amount != self.current_receipt_amount:
txn.amount = self.current_receipt_amount
stripe.PaymentIntent.modify(txn.intent_id, amount = txn.amount)

if self.session:
self.session.add(txn)
self.session.commit()
else:
with Session() as session:
session.add(txn)
session.commit()
if not error:
return txn
else:
return txn


class ReceiptTransaction(MagModel):
Expand Down Expand Up @@ -255,6 +275,34 @@ def is_pending_charge(self):
def stripe_id(self):
# Return the most relevant Stripe ID for admins
return self.refund_id or self.charge_id or self.intent_id

def check_stripe_id(self):
# Check all possible Stripe IDs for invalid request errors
# Stripe IDs become invalid if, for example, the Stripe API keys change

if not self.stripe_id:
return

refund_intent_id = None
if self.refund_id:
try:
refund = stripe.Refund.retrieve(self.refund_id)
except Exception as e:
return e.user_message
else:
refund_intent_id = refund.payment_intent

if self.intent_id or refund_intent_id:
try:
intent = stripe.PaymentIntent.retrieve(self.intent_id or refund_intent_id)
except Exception as e:
return e.user_message

if self.charge_id:
try:
charge = stripe.Charge.retrieve(self.charge_id)
except Exception as e:
return e.user_message

def get_intent_id_from_refund(self):
if not self.refund_id:
Expand Down
1 change: 1 addition & 0 deletions uber/models/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class AutomatedEmail(MagModel, BaseEmailMixin):
needs_approval = Column(Boolean, default=True)
unapproved_count = Column(Integer, default=0)
currently_sending = Column(Boolean, default=False)
last_send_time = Column(UTCDateTime, nullable=True, default=None)

allow_at_the_con = Column(Boolean, default=False)
allow_post_con = Column(Boolean, default=False)
Expand Down
17 changes: 16 additions & 1 deletion uber/models/promo_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,15 @@ def normalized_code(cls):
def email(self):
return self.buyer.email if self.buyer else None

@property
@hybrid_property
def total_cost(self):
return sum(code.cost for code in self.promo_codes if code.cost)

@total_cost.expression
def total_cost(cls):
return select([func.sum(PromoCode.cost)]
).where(PromoCode.group_id == cls.id
).label('total_cost')

@property
def valid_codes(self):
Expand Down Expand Up @@ -361,6 +367,15 @@ def is_expired(self):
def is_expired(cls):
return cls.expiration_date < localized_now()

@hybrid_property
def group_registered(self):
if self.group_id:
return self.group.registered

@group_registered.expression
def group_registered(cls):
return select([PromoCodeGroup.registered]).where(PromoCodeGroup.id == cls.group_id).label('group_registered')

@property
def is_free(self):
return not self.discount or (
Expand Down
6 changes: 6 additions & 0 deletions uber/models/tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ def differences(cls, instance):

@classmethod
def track(cls, action, instance):
from uber.models import AutomatedEmail

if action in [c.CREATED, c.UNPAID_PREREG, c.EDITED_PREREG]:
vals = {
attr: cls.repr(column, getattr(instance, attr))
Expand All @@ -144,6 +146,10 @@ def track(cls, action, instance):
data = cls.format(diff)
if len(diff) == 1 and 'badge_num' in diff and c.SHIFT_CUSTOM_BADGES:
action = c.AUTO_BADGE_SHIFT
if isinstance(instance, AutomatedEmail) and diff.startswith(("currently_sending",
"last_send_time",
"unapproved_count")):
return
elif not data:
return
else:
Expand Down
Loading

0 comments on commit 7e0fa64

Please sign in to comment.