Exemple #1
0
async def company_upload(request):
    field_name = request.match_info['field']
    assert field_name in {'image', 'logo'}, field_name  # double check
    content = await request_image(
        request, expected_size=None if field_name == 'image' else LOGO_SIZE)

    co_id = request['company_id']
    co_slug, old_image = await request['conn'].fetchrow_b(
        'SELECT slug, :image_field FROM companies WHERE id=:id',
        image_field=V(field_name),
        id=co_id)

    upload_path = Path(co_slug) / 'co' / field_name
    method = upload_background if field_name == 'image' else upload_logo
    image_url = await method(content,
                             upload_path=upload_path,
                             settings=request.app['settings'])

    await request['conn'].execute_b('UPDATE companies SET :set WHERE id=:id',
                                    set=V(field_name) == image_url,
                                    id=co_id)

    if old_image:
        await delete_image(old_image, request.app['settings'])
    return json_response(status='success')
Exemple #2
0
 def where(self):
     logic = V('c.company') == self.request['company_id']
     session = self.request['session']
     user_role = session['user_role']
     if user_role != 'admin':
         logic &= V('e.host') == session['user_id']
     return Where(logic)
Exemple #3
0
class UserBread(Bread):
    class Model(BaseModel):
        status: str
        first_name: str
        last_name: str
        role: str  # TODO enum
        email: EmailStr

    browse_enabled = True
    retrieve_enabled = True

    model = Model
    table = 'users'
    browse_order_by_fields = V('active_ts').desc(),
    browse_fields = ('id', V('first_name').cat(RawDangerous("' '")).cat(
        V('last_name')).as_('name'), V('role').as_('role_type'), 'email')
    retrieve_fields = browse_fields + (
        'phone_number',
        'created_ts',
        'active_ts',
    )

    async def check_permissions(self, method):
        await check_session(self.request, 'admin')

    def where(self):
        return Where(V('company') == self.request['company_id'])
Exemple #4
0
 def where(self):
     logic = V('cat.company') == self.request['company_id']
     session = self.request['session']
     if session['role'] != 'admin':
         logic &= V('e.host') == session['user_id']
         if self.method == Method.edit:
             logic &= V('e.start_ts') > funcs.now()
     return Where(logic)
Exemple #5
0
 def select(self) -> Select:
     if self.method == Method.retrieve:
         event_link = Func('event_link', V('cat.slug'), V('e.slug'),
                           V('e.public'),
                           funcs.cast(self.settings.auth_key,
                                      'TEXT')).as_('link')
         return Select(self.retrieve_fields + (event_link, ))
     return super().select()
Exemple #6
0
async def test_manual_logic(conn):
    query, params = render(
        'SELECT :select FROM users WHERE :where ORDER BY :order_by',
        select=select_fields('first_name'),
        where=V('created') > datetime(2021, 1, 1),
        order_by=V('last_name'),
    )
    v = await conn.fetch(query, *params)
    assert ['Fred', 'Joe'] == [r[0] for r in v]
Exemple #7
0
 def join(self):
     joins = Join(
         V('categories').as_('cat').on(V('cat.id') == V('e.category'))
     ) + Join(V('companies').as_('co').on(V('co.id') == V('cat.company')))
     if self.method == Method.retrieve:
         joins += Join(V('users').as_('uh').on(V('uh.id') == V('e.host')))
     return joins
Exemple #8
0
async def test_pool_fetch():
    async with asyncpg.create_pool_b(
            f'postgresql://postgres@localhost/{DB_NAME}') as pool:
        v = await pool.fetchval_b('SELECT :v FROM users ORDER BY id LIMIT 1',
                                  v=funcs.right(V('first_name'), 3))

    assert v == 'red'
Exemple #9
0
async def test_cursor(conn):
    results = []
    q = 'SELECT :s FROM users ORDER BY :o'
    async for r in conn.cursor_b(q,
                                 s=select_fields('first_name'),
                                 o=V('first_name').asc()):
        results.append(tuple(r))
        if len(results) == 2:
            break
    assert results == [('Franks', ), ('Fred', )]
Exemple #10
0
async def test_values_executemany_default(conn):
    co_id = await conn.fetchval('SELECT id FROM companies')
    v = [
        Values(company=co_id,
               first_name='anne',
               last_name=None,
               value=V('DEFAULT')),
        Values(company=co_id,
               first_name='ben',
               last_name='better',
               value=V('DEFAULT')),
        Values(company=co_id,
               first_name='charlie',
               last_name='cat',
               value=V('DEFAULT')),
    ]
    await conn.executemany_b(
        'INSERT INTO users (:values__names) VALUES :values', v)
    assert 6 == await conn.fetchval('SELECT COUNT(*) FROM users')
Exemple #11
0
class UserSelfBread(Bread):
    class Model(BaseModel):
        first_name: str = None
        last_name: str = None
        email: EmailStr
        phone_number: str = None
        receive_emails: bool = None
        allow_marketing: bool = None

    retrieve_enabled = True
    edit_enabled = True

    model = Model
    table = 'users'
    browse_order_by_fields = (V('active_ts').desc(), )
    retrieve_fields = (
        Func('full_name', V('first_name'), V('last_name'),
             V('email')).as_('name'),
        'email',
        V('role').as_('role_type'),
        'status',
        'phone_number',
        'created_ts',
        'receive_emails',
        'allow_marketing',
        'first_name',
        'last_name',
    )

    async def check_permissions(self, method):
        await check_session(self.request, 'host', 'admin')
        if int(self.request.match_info['pk']
               ) != self.request['session']['user_id']:
            raise JsonErrors.HTTPForbidden(message='wrong user')

    async def edit_execute(self, pk, **data):
        await super().edit_execute(pk, **data)
        await record_action(self.request,
                            self.request['session']['user_id'],
                            ActionTypes.edit_profile,
                            changes=data)
        await self.app['donorfy_actor'].update_user(
            pk, update_marketing='allow_marketing' in data)
Exemple #12
0
class DonationOptionBread(Bread):
    class Model(BaseModel):
        category: int
        name: constr(max_length=255)
        amount: condecimal(ge=1, max_digits=6, decimal_places=2)
        live: bool = True
        short_description: str
        long_description: str
        sort_index: int = None

    browse_enabled = True
    retrieve_enabled = True
    edit_enabled = True
    add_enabled = True
    delete_enabled = True

    model = Model
    table = 'donation_options'
    table_as = 'opt'
    browse_order_by_fields = 'opt.category', 'opt.sort_index', 'opt.amount'
    browse_fields = (
        'opt.id',
        'opt.name',
        V('cat.name').as_('category_name'),
        'opt.live',
        'opt.amount',
    )
    retrieve_fields = browse_fields + (
        'opt.category',
        'opt.sort_index',
        'opt.short_description',
        'opt.long_description',
        'opt.image',
    )

    async def check_permissions(self, method):
        await check_session(self.request, 'admin')

    def where(self):
        return Where(V('cat.company') == self.request['company_id'])

    def join(self):
        return Join(
            V('categories').as_('cat').on(V('cat.id') == V('opt.category')))
Exemple #13
0
async def test_multiple_values_execute(conn):
    co_id = await conn.fetchval('SELECT id FROM companies')
    v = MultipleValues(
        Values(company=co_id,
               first_name='anne',
               last_name=None,
               value=3,
               created=datetime(2032, 1, 1)),
        Values(company=co_id,
               first_name='ben',
               last_name='better',
               value=5,
               created=datetime(2032, 1, 2)),
        Values(company=co_id,
               first_name='charlie',
               last_name='cat',
               value=5,
               created=V('DEFAULT')),
    )
    await conn.execute_b('INSERT INTO users (:values__names) VALUES :values',
                         values=v)
    assert 6 == await conn.fetchval('SELECT COUNT(*) FROM users')
Exemple #14
0
def test_args():
    query, params = render('WHERE :a', a=V('a') == True)  # noqa: E712
    assert query == 'WHERE a = $1'
    assert params == [True]
    assert params[0] is True
Exemple #15
0
def test_falsey_values(value, expected):
    query, params = render('WHERE :a', a=V('a') == value)
    assert query == 'WHERE a = $1'
    assert params == [expected]
    assert type(params[0]) == type(expected)
Exemple #16
0
def test_where():
    query, params = render(':v',
                           v=clauses.Where((V('x') == 4)
                                           & (V('y').like('xxx'))))
    assert 'WHERE x = $1 AND y LIKE $2' == query
    assert [4, 'xxx'] == params
Exemple #17
0
import pytest

from buildpg import V, clauses, render


@pytest.mark.parametrize(
    'block,expected_query,expected_params',
    [
        (lambda: clauses.Select(['foo', 'bar']), 'SELECT foo, bar', []),
        (lambda: clauses.Select(['foo', 'bar']), 'SELECT foo, bar', []),
        (lambda: clauses.Select(V('foo').comma(V('bar'))), 'SELECT foo, bar',
         []),
        (lambda: clauses.Select([V('foo').as_(
            'x'), V('bar').as_('y')]), 'SELECT foo AS x, bar AS y', []),
        (lambda: clauses.From('foobar'), 'FROM foobar', []),
        (lambda: clauses.From('foo', 'bar'), 'FROM foo, bar', []),
        (lambda: clauses.From('foo', V('bar')), 'FROM foo, bar', []),
        (lambda: clauses.Join('foobar',
                              V('x.id') == V('y.id')),
         'JOIN foobar ON x.id = y.id', []),
        (lambda: clauses.CrossJoin('xxx'), 'CROSS JOIN xxx', []),
        (lambda: clauses.From('a') + clauses.Join('b') + clauses.Join('c'),
         'FROM a\nJOIN b\nJOIN c', []),
        (lambda: clauses.OrderBy('apple',
                                 V('pear').desc()),
         'ORDER BY apple, pear DESC', []),
        (lambda: clauses.Limit(20), 'LIMIT $1', [20]),
        (lambda: clauses.Offset(20), 'OFFSET $1', [20]),
        (lambda: clauses.Join('foobar',
                              V('x.value') == 0),
         'JOIN foobar ON x.value = $1', [0]),
Exemple #18
0
class EventBread(Bread):
    class Model(BaseModel):
        name: constr(max_length=63)
        category: int
        public: bool = True

        class DateModel(BaseModel):
            dt: datetime
            dur: Optional[int]

        date: DateModel

        class LocationModel(BaseModel):
            lat: float
            lng: float
            name: constr(max_length=63)

        location: LocationModel
        ticket_limit: int = None
        long_description: str

    browse_enabled = True
    retrieve_enabled = True
    add_enabled = True
    edit_enabled = True

    model = Model
    table = 'events'
    table_as = 'e'

    browse_fields = (
        'e.id',
        'e.name',
        V('c.name').as_('category'),
        'e.highlight',
        'e.start_ts',
        funcs.extract(V('epoch').from_(V('e.duration'))).cast('int').as_('duration'),
    )
    retrieve_fields = browse_fields + (
        'e.slug',
        V('c.slug').as_('cat_slug'),
        'e.public',
        'e.status',
        'e.ticket_limit',
        'e.location_name',
        'e.location_lat',
        'e.location_lng',
        'e.long_description',
    )

    async def check_permissions(self, method):
        await check_session(self.request, 'admin', 'host')

    def join(self):
        return Join(V('categories').as_('c').on(V('c.id') == V('e.category')))

    def where(self):
        logic = V('c.company') == self.request['company_id']
        session = self.request['session']
        user_role = session['user_role']
        if user_role != 'admin':
            logic &= V('e.host') == session['user_id']
        return Where(logic)

    def prepare(self, data):
        date = data.pop('date', None)
        if date:
            dt: datetime = date['dt']
            duration: Optional[int] = date['dur']
            data.update(
                start_ts=datetime(dt.year, dt.month, dt.day) if duration is None else dt.replace(tzinfo=None),
                duration=duration and timedelta(seconds=duration),
            )

        loc = data.pop('location', None)
        if loc:
            data.update(
                location_name=loc['name'],
                location_lat=loc['lat'],
                location_lng=loc['lng'],
            )

        long_desc = data.get('long_description')
        if long_desc is not None:
            data['short_description'] = shorten(long_desc, width=140, placeholder='…')
        return data

    def prepare_add_data(self, data):
        data = self.prepare(data)
        data.update(
            slug=slugify(data['name']),
            host=self.request['session'].get('user_id'),
        )
        return data

    def prepare_edit_data(self, data):
        return self.prepare(data)
Exemple #19
0
 def join(self):
     return Join(V('companies').as_('co').on(V('u.company') == V('co.id')))
Exemple #20
0
 def join(self):
     return Join(V('categories').as_('cat').on(V('cat.id') == V('opt.category')))
Exemple #21
0
class EventBread(Bread):
    class Model(BaseModel):
        name: constr(max_length=150)
        category: int
        public: bool = True
        timezone: TzInfo
        date: DateModel

        mode: EventMode = EventMode.tickets

        class LocationModel(BaseModel):
            lat: float
            lng: float
            name: constr(max_length=63)

        location: LocationModel = None
        ticket_limit: PositiveInt = None
        price: condecimal(ge=1, max_digits=6, decimal_places=2) = None
        suggested_donation: condecimal(ge=1, max_digits=6,
                                       decimal_places=2) = None
        donation_target: condecimal(ge=0, max_digits=9,
                                    decimal_places=2) = None
        long_description: str
        short_description: str = None
        youtube_video_id: str = None
        description_image: str = None
        description_intro: str = None
        external_ticket_url: HttpUrl = None
        external_donation_url: HttpUrl = None

        @validator('public', pre=True)
        def none_bool(cls, v):
            return v or False

    browse_enabled = True
    retrieve_enabled = True
    add_enabled = True
    edit_enabled = True
    delete_enabled = True

    model = Model
    table = 'events'
    table_as = 'e'

    browse_fields = (
        'e.id',
        'e.name',
        V('cat.name').as_('category'),
        'e.status',
        'e.highlight',
        Func('as_time_zone', V('e.start_ts'), V('e.timezone')).as_('start_ts'),
        funcs.extract(V('epoch').from_(
            V('e.duration'))).cast('int').as_('duration'),
    )
    browse_order_by_fields = (V('e.start_ts').desc(), )
    retrieve_fields = browse_fields + (
        V('cat.id').as_('cat_id'),
        'e.public',
        'e.allow_tickets',
        'e.allow_donations',
        'e.image',
        'e.secondary_image',
        'e.ticket_limit',
        'e.donation_target',
        'e.location_name',
        'e.location_lat',
        'e.location_lng',
        'e.youtube_video_id',
        'e.short_description',
        'e.long_description',
        'e.description_image',
        'e.description_intro',
        'e.external_ticket_url',
        'e.external_donation_url',
        'e.host',
        'e.timezone',
        Func('full_name', V('uh.first_name'), V('uh.last_name'),
             V('uh.email')).as_('host_name'),
    )

    async def check_permissions(self, method):
        if method == Method.delete:
            await check_session(self.request, 'admin')
        else:
            await check_session(self.request, 'admin', 'host')

    def select(self) -> Select:
        if self.method == Method.retrieve:
            event_link = Func('event_link', V('cat.slug'), V('e.slug'),
                              V('e.public'),
                              funcs.cast(self.settings.auth_key,
                                         'TEXT')).as_('link')
            return Select(self.retrieve_fields + (event_link, ))
        return super().select()

    def join(self):
        joins = Join(
            V('categories').as_('cat').on(V('cat.id') == V('e.category'))
        ) + Join(V('companies').as_('co').on(V('co.id') == V('cat.company')))
        if self.method == Method.retrieve:
            joins += Join(V('users').as_('uh').on(V('uh.id') == V('e.host')))
        return joins

    def where(self):
        logic = V('cat.company') == self.request['company_id']
        session = self.request['session']
        if session['role'] != 'admin':
            logic &= V('e.host') == session['user_id']
            if self.method == Method.edit:
                logic &= V('e.start_ts') > funcs.now()
        return Where(logic)

    def prepare(self, data):
        if self.request['session']['role'] != 'admin':
            if data.get('external_ticket_url'):
                raise JsonErrors.HTTPForbidden(
                    message='external_ticket_url may only be set by admins')
            elif data.get('external_donation_url'):
                raise JsonErrors.HTTPForbidden(
                    message='external_donation_url may only be set by admins')

        timezone: TzInfo = data.pop('timezone', None)
        if timezone:
            data['timezone'] = str(timezone)
        date = data.pop('date', None)
        if date:
            dt, duration = prepare_event_start(date['dt'], date['dur'],
                                               timezone)
            data.update(
                start_ts=dt,
                duration=duration,
            )

        loc = data.pop('location', None)
        if loc:
            data.update(
                location_name=loc['name'],
                location_lat=loc['lat'],
                location_lng=loc['lng'],
            )

        return data

    async def prepare_add_data(self, data):
        data = self.prepare(data)

        session = self.request['session']
        mode: EventMode = data.pop('mode', EventMode.tickets)
        data.update(
            slug=slugify(data['name'], 63),
            short_description=shorten(clean_markdown(data['long_description']),
                                      width=140,
                                      placeholder='…'),
            host=session['user_id'],
            allow_tickets=mode in (EventMode.tickets, EventMode.both),
            allow_donations=mode in (EventMode.donations, EventMode.both),
        )

        q = 'SELECT status FROM users WHERE id=$1'
        if session['role'] == 'admin' or 'active' == await self.conn.fetchval(
                q, session['user_id']):
            data['status'] = 'published'
        return data

    async def prepare_edit_data(self, pk, data):
        timezone: TzInfo = data.get('timezone')
        if not timezone and 'date' in data:
            # timezone is needed when date is being updated
            tz = await self.conn.fetchval(
                'SELECT timezone FROM events WHERE id=$1', pk)
            data['timezone'] = pytz.timezone(tz)

        data = self.prepare(data)

        if timezone and 'start_ts' not in data:
            # timezone has changed but not start_ts, need to update start_ts to account for timezone change
            dt = await self.conn.fetchval(
                "SELECT start_ts AT TIME ZONE timezone FROM events WHERE id=$1",
                pk)
            data['start_ts'] = timezone.localize(dt)

        mode: EventMode = data.pop('mode', None)
        if mode is not None:
            data.update(
                allow_tickets=mode in (EventMode.tickets, EventMode.both),
                allow_donations=mode in (EventMode.donations, EventMode.both),
            )
        return data

    add_sql = """
    INSERT INTO :table (:values__names) VALUES :values
    ON CONFLICT (category, slug) DO NOTHING
    RETURNING :pk_field
    """

    async def add_execute(self, *, slug, **data):
        price = data.pop('price', None)
        # we always create a suggested donation and the amount cannot be blank
        suggested_donation = data.pop('suggested_donation', price or 10)

        async with self.conn.transaction():
            pk = await super().add_execute(slug=slug, **data)
            while pk is None:
                # event with this slug already exists
                pk = await super().add_execute(slug=slug + '-' +
                                               pseudo_random_str(4),
                                               **data)

            # always create both a ticket type and a suggested donation in case the mode of the event changes in future
            await self.conn.execute_b(
                'INSERT INTO ticket_types (:values__names) VALUES :values',
                values=MultipleValues(
                    Values(event=pk,
                           name='Standard',
                           price=price,
                           mode='ticket',
                           custom_amount=False),
                    Values(event=pk,
                           name='Standard',
                           price=suggested_donation,
                           mode='donation',
                           custom_amount=False),
                    Values(event=pk,
                           name='Custom Amount',
                           price=None,
                           mode='donation',
                           custom_amount=True),
                ),
            )
            action_id = await record_action_id(
                self.request,
                self.request['session']['user_id'],
                ActionTypes.create_event,
                event_id=pk)
        await self.app['email_actor'].send_event_created(action_id)
        await self.app['donorfy_actor'].event_created(pk)
        return pk

    async def edit_execute(self, pk, **data):
        try:
            await super().edit_execute(pk, **data)
        except CheckViolationError as exc:
            if exc.constraint_name != 'ticket_limit_check':  # pragma: no branch
                raise  # pragma: no cover
            raise JsonErrors.HTTPBadRequest(
                message='Invalid Ticket Limit',
                details=[{
                    'loc': ['ticket_limit'],
                    'msg':
                    f'May not be less than the number of tickets already booked.',
                    'type': 'value_error.too_low',
                }],
            )
        else:
            await record_action(
                self.request,
                self.request['session']['user_id'],
                ActionTypes.edit_event,
                event_id=pk,
                subtype='edit-event',
            )
            await self.app['email_actor'].send_tickets_available(pk)
Exemple #22
0
 def where(self):
     return Where(V('company') == self.request['company_id'])
Exemple #23
0
 def join(self):
     return Join(V('categories').as_('c').on(V('c.id') == V('e.category')))
Exemple #24
0
 {
     'template': 'inv: :var',
     'var': lambda: ~(S(1) % 2),
     'expected_query': 'inv: not($1 % $2)',
     'expected_params': [1, 2],
 },
 {
     'template': 'double inv: :var',
     'var': lambda: ~~(S(1) % 2),
     'expected_query': 'double inv: $1 % $2',
     'expected_params': [1, 2],
 },
 {'template': 'eq: :var', 'var': lambda: Var('foo') > 2, 'expected_query': 'eq: foo > $1', 'expected_params': [2]},
 {
     'template': 'chain: :var',
     'var': lambda: V('x') + 4 + 2 + 1,
     'expected_query': 'chain: x + $1 + $2 + $3',
     'expected_params': [4, 2, 1],
 },
 {
     'template': 'complex: :var',
     'var': lambda: (Var('foo') > 2) & ((SqlBlock('x') / 7 > 3) | (Var('another') ** 4 == 42)),
     'expected_query': 'complex: foo > $1 AND ($2 / $3 > $4 OR another ^ $5 = $6)',
     'expected_params': [2, 'x', 7, 3, 4, 42],
 },
 {
     'template': 'complex AND OR: :var',
     # as above but using the AND and OR functions for clear usage
     'var': lambda: funcs.AND(V('foo') > 2, funcs.OR(S('x') / 7 > 3, V('another') ** 4 == 42)),
     'expected_query': 'complex AND OR: foo > $1 AND ($2 / $3 > $4 OR another ^ $5 = $6)',
     'expected_params': [2, 'x', 7, 3, 4, 42],
Exemple #25
0
class UserBread(Bread):
    class Model(BaseModel):
        first_name: str = None
        last_name: str = None
        email: EmailStr
        role_type: UserRoles
        phone_number: str = None
        receive_emails: bool = True
        allow_marketing: bool = False

    browse_enabled = True
    retrieve_enabled = True
    edit_enabled = True
    add_enabled = True

    model = Model
    table = 'users'
    table_as = 'u'
    browse_order_by_fields = V('u.active_ts').desc(), V('id').asc()
    browse_fields = (
        'u.id',
        Func('full_name', V('u.first_name'), V('u.last_name'),
             V('u.email')).as_('name'),
        V('u.role').as_('role_type'),
        'u.status',
        'u.email',
        Func('as_time_zone', V('u.active_ts'),
             V('co.display_timezone')).as_('active_ts'),
    )
    retrieve_fields = browse_fields + (
        'u.status',
        'u.phone_number',
        Func('as_time_zone', V('u.created_ts'),
             V('co.display_timezone')).as_('created_ts'),
        'u.active_ts',
        'u.receive_emails',
        'u.allow_marketing',
        'u.first_name',
        'u.last_name',
    )

    async def check_permissions(self, method):
        await check_session(self.request, 'admin')

    def where(self):
        return Where(V('company') == self.request['company_id'])

    def join(self):
        return Join(V('companies').as_('co').on(V('u.company') == V('co.id')))

    async def prepare_add_data(self, data):
        role_type = data.pop('role_type')
        if role_type not in {UserRoles.host, UserRoles.admin}:
            raise JsonErrors.HTTPBadRequest(
                message='role must be either "host" or "admin".')
        data.update(role=role_type, company=self.request['company_id'])
        return data

    async def prepare_edit_data(self, pk, data):
        role_type = data.pop('role_type', None)
        if role_type:
            data['role'] = role_type
        return data

    async def add_execute(self, **data):
        pk = await super().add_execute(**data)
        await self.app['email_actor'].send_account_created(
            pk, created_by_admin=True)
        return pk

    async def edit_execute(self, pk, **data):
        await super().edit_execute(pk, **data)
        await self.app['donorfy_actor'].update_user(
            pk, update_marketing='allow_marketing' in data)