Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graphql endpoint #162

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions boxoffice/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
from flask_lastuser import Lastuser
from flask_lastuser.sqlalchemy import UserManager
from flask_admin import Admin
from flask_graphql import GraphQLView
import wtforms_json
from baseframe import baseframe, assets, Version
from ._version import __version__
import coaster.app


app = Flask(__name__, instance_relative_config=True)
lastuser = Lastuser()

Expand All @@ -29,7 +29,7 @@
from . import extapi, views # NOQA
from boxoffice.models import db, User, Item, Price, DiscountPolicy, DiscountCoupon, ItemCollection, Organization, Category # noqa
from siteadmin import ItemCollectionModelView, ItemModelView, PriceModelView, DiscountPolicyModelView, DiscountCouponModelView, OrganizationModelView, CategoryModelView # noqa

from boxoffice.views.graphql import schema as graphql_schema

# Configure the app
coaster.app.init_app(app)
Expand All @@ -46,6 +46,15 @@
mail.init_app(app)
wtforms_json.init()

app.add_url_rule(
'/graphql',
view_func=lastuser.requires_login(GraphQLView.as_view(
'graphql',
schema=graphql_schema,
graphiql=app.debug
))
)

# This is a temporary solution for an admin interface, only
# to be used until the native admin interface is ready.
try:
Expand Down
17 changes: 17 additions & 0 deletions boxoffice/models/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,20 @@ class Category(BaseScopedNameMixin, db.Model):
backref=db.backref('categories', cascade='all, delete-orphan',
order_by=seq, collection_class=ordering_list('seq', count_from=1)))
parent = db.synonym('item_collection')

__roles__ = {
'category_owner': {
'write': {},
'read': {'id', 'name', 'title'}
}
}

def roles_for(self, user=None, token=None):
if not user and not token:
return set()
roles = super(Category, self).roles_for(user, token)
if user or token:
roles.add('user')
if self.item_collection.organization.userid in user.organizations_owned_ids():
roles.add('category_owner')
return roles
18 changes: 18 additions & 0 deletions boxoffice/models/item_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from ..models import db, BaseScopedNameMixin, Organization, MarkdownColumn


__all__ = ['ItemCollection']


Expand All @@ -18,3 +19,20 @@ class ItemCollection(BaseScopedNameMixin, db.Model):
organization_id = db.Column(db.Integer, db.ForeignKey('organization.id'), nullable=False)
organization = db.relationship(Organization, backref=db.backref('item_collections', cascade='all, delete-orphan'))
parent = db.synonym('organization')

__roles__ = {
'item_collection_owner': {
'write': {},
'read': {'id', 'name', 'title', 'categories'}
}
}

def roles_for(self, user=None, token=None):
if not user and not token:
return set()
roles = super(ItemCollection, self).roles_for(user, token)
if user or token:
roles.add('user')
if self.organization.userid in user.organizations_owned_ids():
roles.add('item_collection_owner')
return roles
30 changes: 28 additions & 2 deletions boxoffice/models/line_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
from collections import namedtuple, OrderedDict
from sqlalchemy.sql import select, func
from sqlalchemy.ext.orderinglist import ordering_list
from isoweek import Week
from boxoffice.models import db, JsonDict, BaseMixin, ItemCollection, Order, Item, DiscountPolicy, DISCOUNT_TYPE, DiscountCoupon, OrderSession
from coaster.utils import LabeledEnum, isoweek_datetime, midnight_to_utc
from coaster.roles import set_roles
from baseframe import __
from isoweek import Week
from boxoffice.models import db, JsonDict, BaseMixin, ItemCollection, Order, Item, DiscountPolicy, DISCOUNT_TYPE, DiscountCoupon, OrderSession

__all__ = ['LineItem', 'LINE_ITEM_STATUS', 'Assignee', 'LineItemDiscounter']

Expand All @@ -22,6 +23,7 @@ class LINE_ITEM_STATUS(LabeledEnum):
#: a line item. Eg: a discount no longer applicable on a line item as a result of a cancellation
VOID = (3, __("Void"))

LINE_ITEM_STATUS.TRANSACTION = [LINE_ITEM_STATUS.CONFIRMED, LINE_ITEM_STATUS.CANCELLED]

LineItemTuple = namedtuple('LineItemTuple', ['item_id', 'id', 'base_amount', 'discount_policy_id', 'discount_coupon_id', 'discounted_amount', 'final_amount'])

Expand Down Expand Up @@ -92,6 +94,24 @@ class LineItem(BaseMixin, db.Model):
ordered_at = db.Column(db.DateTime, nullable=True)
cancelled_at = db.Column(db.DateTime, nullable=True)

__roles__ = {
'order_owner': {
'write': {},
'read': {'id', 'base_amount', 'discounted_amount', 'final_amount',
'discount_policy_id', 'discounted_coupon_id', 'ordered_at'}
}
}

def roles_for(self, user=None, token=None):
if not user and not token:
return set()
roles = super(LineItem, self).roles_for(user, token)
if user or token:
roles.add('user')
if self.order.item_collection.organization.userid in user.organizations_owned_ids():
roles.add('order_owner')
return roles

def permissions(self, user, inherited=None):
perms = super(LineItem, self).permissions(user, inherited)
if self.order.organization.userid in user.organizations_owned_ids():
Expand Down Expand Up @@ -326,6 +346,12 @@ def get_confirmed_line_items(self):
Order.get_confirmed_line_items = property(get_confirmed_line_items)


@set_roles(read={'all', 'order_owner'})
def get_transacted_line_items(self):
return LineItem.query.filter(LineItem.order == self, LineItem.status.in_(LINE_ITEM_STATUS.TRANSACTION))
Order.get_transacted_line_items = get_transacted_line_items


def get_from_item(cls, item, qty, coupon_codes=[]):
"""
Returns a list of (discount_policy, discount_coupon) tuples
Expand Down
28 changes: 27 additions & 1 deletion boxoffice/models/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
from datetime import datetime
from collections import namedtuple
from sqlalchemy import sql
from boxoffice.models import db, BaseMixin, User
from boxoffice.models import db, BaseMixin, User, ItemCollection
from coaster.utils import LabeledEnum, buid
from coaster.roles import set_roles
from baseframe import __

__all__ = ['Order', 'ORDER_STATUS', 'OrderSession']
Expand Down Expand Up @@ -64,6 +65,23 @@ class Order(BaseMixin, db.Model):

invoice_no = db.Column(db.Integer, nullable=True)

__roles__ = {
'order_owner': {
'write': {},
'read': {'id', 'buyer_email', 'buyer_fullname', 'buyer_phone'}
}
}

def roles_for(self, user=None, token=None):
if not user and not token:
return set()
roles = super(Order, self).roles_for(user, token)
if user or token:
roles.add('user')
if self.item_collection.organization.userid in user.organizations_owned_ids():
roles.add('order_owner')
return roles

def permissions(self, user, inherited=None):
perms = super(Order, self).permissions(user, inherited)
if self.organization.userid in user.organizations_owned_ids():
Expand Down Expand Up @@ -115,6 +133,14 @@ def is_fully_assigned(self):
return True


@set_roles(read={'all', 'item_collection_owner'})
def get_transacted_orders(self):
"""Returns a SQLAlchemy query object preset with an item collection's transacted orders"""
return Order.query.filter(Order.item_collection == self, Order.status.in_(ORDER_STATUS.TRANSACTION))

ItemCollection.get_transacted_orders = get_transacted_orders


class OrderSession(BaseMixin, db.Model):
"""
Records the referrer and utm headers for an order
Expand Down
2 changes: 1 addition & 1 deletion boxoffice/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-

from . import admin, item_collection, order, participant, login, admin_item_collection, admin_order, admin_discount, admin_report, admin_item
from . import admin, item_collection, order, participant, login, admin_item_collection, admin_order, admin_discount, admin_report, admin_item, graphql
64 changes: 64 additions & 0 deletions boxoffice/views/graphql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from flask import g
import graphene
from ..models import ItemCollection, Order


class LineItemType(graphene.ObjectType):
id = graphene.String()
line_item_seq = graphene.Int()
base_amount = graphene.Float()
discounted_amount = graphene.Float()
final_amount = graphene.Float()
discount_coupon_id = graphene.String()
discount_policy_id = graphene.String()


class OrderType(graphene.ObjectType):
id = graphene.String()
invoice_no = graphene.Int()
buyer_email = graphene.String()
buyer_fullname = graphene.String()
buyer_phone = graphene.String()
line_items = graphene.List(LineItemType)

def resolve_line_items(self, args, context, info):
line_items = self.get('get_transacted_line_items') and self['get_transacted_line_items']().all()
return [line_item.access_for(user=g.user) for line_item in line_items]


class CategoryType(graphene.ObjectType):
id = graphene.Int()
name = graphene.String()
title = graphene.String()


class ItemCollectionType(graphene.ObjectType):
id = graphene.String()
name = graphene.String()
title = graphene.String()
categories = graphene.List(CategoryType)
orders = graphene.List(OrderType)
order = graphene.Field(OrderType, id=graphene.String())

def resolve_categories(self, args, context, info):
return [category.access_for(user=g.user) for category in self.categories]

def resolve_order(self, args, context, info):
order = Order.query.get(args.get('id'))
return order.access_for(user=g.user)

def resolve_orders(self, args, context, info):
orders = self.get('get_transacted_orders') and self['get_transacted_orders']().all()
return [order.access_for(user=g.user) for order in orders]


class QueryType(graphene.ObjectType):
name = 'Query'
item_collection = graphene.Field(ItemCollectionType, id=graphene.String())

def resolve_item_collection(self, args, context, info):
item_collection = ItemCollection.query.get(args.get('id'))
return item_collection.access_for(user=g.user)


schema = graphene.Schema(query=QueryType)
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ Flask-Admin
unicodecsv
isoweek
Flask-Migrate
graphene[sqlalchemy]
Flask-GraphQL==1.3.0