Skip to content

Commit 55ecbd1

Browse files
committed
added first views of rest api, added config and misc files
1 parent f39296f commit 55ecbd1

13 files changed

+384
-12
lines changed

README.md

+28-6
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,14 @@ That's all folks.
5252

5353
* INIT: crawl through and index whole site - i.e. we will get url for each order
5454
This function will be handy if we have to start from scratch
55-
* parse orders so that we know the customer, the price and discount info + shipping details
55+
* _**parse orders so that we know the customer, the price and discount info + shipping details**_
5656
* add argument parser so that the program can be used from command line
5757

5858
### DB
5959

6060
* _**integration with db - sqlite3 would be enough (using peewee)**_
6161
* _**create relevant models and convenience methods for easy data manipulation**_
62-
* store and use relevant data for faster data fetching
62+
* _**store and use relevant data for faster data fetching**_
6363
* allow updates on records, allow marking finished to deny updates if impossible
6464

6565

@@ -72,7 +72,7 @@ That's all folks.
7272
### Cron jobs
7373

7474
* must be available through PhantomJS or other driver not requiring GUI **!**
75-
* allow regular checks for new orders
75+
* allow regular checks for new orders (require recording the last updated order)
7676
* automatic evaluation if user should get voucher
7777
* auto create voucher and send to user
7878

@@ -81,10 +81,32 @@ That's all folks.
8181

8282
* get voucher status
8383
* _create voucher_
84-
* use voucher in brick shop
85-
* convert points from brick shop to voucher
84+
* use voucher in brick shop (=deactivate on in shoptet)
85+
* convert points from brick shop to voucher
8686

8787

8888
### Client interface
8989

90-
* server based api (flask rest?) to access db from third party apps (django: verne?)
90+
* server based api (flask rest?) to access db from third party apps (django: verne?)
91+
92+
93+
94+
### SHOPTET API
95+
96+
fetch - get from url and save to db (if save=True), return dictionary
97+
get - get from db (and update if fetch=True), return object
98+
99+
`fetch_first_order()`
100+
101+
`fetch_next_order(order=None)`
102+
103+
`fetch_order(id=?, shop_id=?, url=?)`
104+
105+
`get_last_added_order()`
106+
107+
`fetch_next_link(order=None)` - open order in browser and get url of next order,
108+
if order is None, use the last added order, if none added, use the very first
109+
110+
`fetch_close_date(order)`: open in browser, click on history and update activity status/close date
111+
112+
logging - create order, update order

__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from shoptet_api import models
2-
from shoptet_api import utils
3-
from shoptet_api import shoptet
1+
from shoptet_api import (config, models, app, auth, models, utils,
2+
shoptet, api, serializer, views
3+
)

admin.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import datetime
2+
from flask import request, redirect
3+
4+
from flask_peewee.admin import Admin, ModelAdmin, AdminPanel
5+
from flask_peewee.filters import QueryFilter
6+
7+
from shoptet_api.app import app, db
8+
from shoptet_api.auth import authentification
9+
from shoptet_api.models import User, Order, Log, Voucher, Shop, SysUser
10+
11+
12+
admin = Admin(app, authentification, branding='Example Site')
13+
14+
15+
class UserAdmin(ModelAdmin):
16+
columns = ('email', 'name', 'last_name',)
17+
# foreign_key_lookups = {'user': 'email'}
18+
# filter_fields = ('user', 'content', 'pub_date', 'user__username')
19+
20+
21+
class OrderAdmin(ModelAdmin):
22+
columns = ('user_id', 'order_num', 'open_date', 'close_date', 'total_price')
23+
# exclude = ('created_date',)
24+
25+
26+
class LogAdmin(ModelAdmin):
27+
columns = ('log_time', 'event', 'model', 'record', 'user_id', 'details')
28+
29+
30+
authentification.register_admin(admin)
31+
admin.register(User, UserAdmin)
32+
admin.register(Order, OrderAdmin)
33+
admin.register(Log, LogAdmin)

api.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from flask_peewee.rest import RestAPI, RestResource, UserAuthentication, AdminAuthentication, RestrictOwnerResource
2+
3+
from shoptet_api.app import app
4+
from shoptet_api.auth import authentification
5+
from shoptet_api.models import User, Order, OrderLine, Voucher, Log, Shop
6+
7+
8+
user_auth = UserAuthentication(authentification)
9+
admin_auth = AdminAuthentication(authentification)
10+
11+
# instantiate our api wrapper
12+
api = RestAPI(app, default_auth=user_auth)
13+
14+
15+
class UserResource(RestResource):
16+
# exclude = ('password', 'email',)
17+
pass
18+
19+
class OrderResource(RestResource):
20+
# owner_field = 'user'
21+
include_resources = {'user_id': UserResource}
22+
23+
24+
class OrderLineResource(RestResource):
25+
include_resources = {'order_id': OrderResource}
26+
27+
28+
class LogResource(RestResource):
29+
pass
30+
# include_resources = {
31+
# 'from_user': UserResource,
32+
# 'to_user': UserResource,
33+
# }
34+
# paginate_by = None
35+
36+
class ShopResource(RestResource):
37+
pass
38+
39+
class VoucherResource(RestResource):
40+
pass
41+
42+
# register our models so they are exposed via /api/<model>/
43+
api.register(User, UserResource, auth=admin_auth)
44+
api.register(Order, OrderResource)
45+
api.register(OrderLine, OrderLineResource)
46+
api.register(Log, LogResource)
47+
api.register(Voucher, VoucherResource)
48+
api.register(Shop, ShopResource)

app.py

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from flask import Flask
2+
3+
# flask-peewee bindings
4+
from flask_peewee.db import Database
5+
from peewee import SqliteDatabase
6+
7+
8+
app = Flask(__name__)
9+
app.config.from_object('config.Configuration')
10+
11+
# db = Database(app)
12+
db = SqliteDatabase('shoptet.db')
13+
14+
# from shoptet_api.models import SysUser, User, Order, OrderLine, Log, Voucher, Shop
15+
#
16+
# def create_tables():
17+
# SysUser.create_table()
18+
# User.create_table()
19+
# Order.create_table()
20+
# OrderLine.create_table()
21+
# Log.create_table()
22+
# Voucher.create_table()
23+
# Shop.create_table()

auth.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from flask_peewee.auth import Auth
2+
3+
from shoptet_api.app import app, db
4+
from shoptet_api.models import SysUser
5+
6+
7+
authentification = Auth(app, db, user_model=SysUser)

config.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class Configuration(object):
2+
DATABASE = {
3+
'name': 'shoptet.db',
4+
'engine': 'peewee.SqliteDatabase',
5+
'check_same_thread': False,
6+
}
7+
DEBUG = True
8+
SECRET_KEY = 'replace-this'

main.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from shoptet_api.app import app, db
2+
3+
from shoptet_api.auth import *
4+
from shoptet_api.admin import admin
5+
from shoptet_api.api import api
6+
from shoptet_api.models import *
7+
from shoptet_api.views import *
8+
9+
admin.setup()
10+
api.setup()
11+
12+
13+
if __name__ == '__main__':
14+
app.run()

models.py

+42-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
import random
44
import re
55
import string
6+
import uuid
67

78
from peewee import *
9+
from flask_peewee.utils import get_dictionary_from_model
10+
from shoptet_api.app import db
811

9-
db = SqliteDatabase('shoptet.db')
12+
# db = SqliteDatabase('shoptet.db')
1013

1114

1215
# TODO make mapping from shoptet to choices (regex?)
@@ -43,14 +46,51 @@ class DBModel(Model):
4346
class Meta:
4447
database = db
4548

49+
date_format = '%Y-%m-%d'
50+
time_format = '%H:%M:%S'
51+
datetime_format = ' '.join([date_format, time_format])
52+
4653
create_date = DateTimeField(default=datetime.datetime.now)
4754
write_date = DateTimeField(default=datetime.datetime.now)
4855

56+
def convert_value(self, value):
57+
if isinstance(value, datetime.datetime):
58+
return value.strftime(self.datetime_format)
59+
elif isinstance(value, datetime.date):
60+
return value.strftime(self.date_format)
61+
elif isinstance(value, datetime.time):
62+
return value.strftime(self.time_format)
63+
elif isinstance(value, Model):
64+
return value._pk
65+
elif isinstance(value, (uuid.UUID, Decimal)):
66+
return str(value)
67+
else:
68+
return value
69+
70+
def clean_data(self, data):
71+
for key, value in data.items():
72+
if isinstance(value, dict):
73+
self.clean_data(value)
74+
elif isinstance(value, (list, tuple)):
75+
data[key] = map(self.clean_data, value)
76+
else:
77+
data[key] = self.convert_value(value)
78+
return data
79+
4980
def save(self, *args, **kwargs):
5081
_now = datetime.datetime.now()
5182
self.__data__.update({'write_date': _now})
5283
return super().save(*args, **kwargs)
5384

85+
def serialize(self, fields=None, exclude=None):
86+
data = get_dictionary_from_model(self, fields, exclude)
87+
return self.clean_data(data)
88+
89+
@property
90+
def serialized(self):
91+
# returns all fields
92+
return self.serialize()
93+
5494

5595
class SysUser(DBModel):
5696
username = CharField()
@@ -127,7 +167,7 @@ class Voucher(DBModel):
127167
amount = DecimalField(null=True)
128168
valid_from = DateField(null=True)
129169
valid_to = DateField(null=True)
130-
user_id = ForeignKeyField(User, backref='orders')
170+
user_id = ForeignKeyField(User, backref='vouchers')
131171

132172

133173
class Log(DBModel):

serializer.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import datetime
2+
import sys
3+
import uuid
4+
5+
from peewee import Model
6+
from flask_peewee.utils import get_dictionary_from_model, get_model_from_dictionary
7+
8+
9+
class Serializer(object):
10+
date_format = '%Y-%m-%d'
11+
time_format = '%H:%M:%S'
12+
datetime_format = ' '.join([date_format, time_format])
13+
14+
def convert_value(self, value):
15+
if isinstance(value, datetime.datetime):
16+
return value.strftime(self.datetime_format)
17+
elif isinstance(value, datetime.date):
18+
return value.strftime(self.date_format)
19+
elif isinstance(value, datetime.time):
20+
return value.strftime(self.time_format)
21+
elif isinstance(value, Model):
22+
return value._pk
23+
elif isinstance(value, uuid.UUID):
24+
return str(value)
25+
else:
26+
return value
27+
28+
def clean_data(self, data):
29+
for key, value in data.items():
30+
if isinstance(value, dict):
31+
self.clean_data(value)
32+
elif isinstance(value, (list, tuple)):
33+
data[key] = map(self.clean_data, value)
34+
else:
35+
data[key] = self.convert_value(value)
36+
return data
37+
38+
def serialize_object(self, obj, fields=None, exclude=None):
39+
data = get_dictionary_from_model(obj, fields, exclude)
40+
return self.clean_data(data)
41+
42+
def serialize_list(self, lst, fields=None, exclude=None):
43+
return [self.clean_data(get_dictionary_from_model(obj, fields, exclude)) for obj in lst]
44+
45+
46+
class Deserializer(object):
47+
def deserialize_object(self, model, data):
48+
return get_model_from_dictionary(model, data)

shoptet.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ def read_order_from_url(self, url):
171171
# this is to ensure we can get those independently from sheer url
172172
r = re.search(r'\?id=(?P<order_id>\d+)', url)
173173
order_details['shop_order_id'] = r.group('order_id') if r else None
174+
# todo handle last order!
174175
order_details['order_num'] = soup.h1.strong.text
175176
order_details['url'] = url
176177
order_details['next_url'] = self.get_next_url_from_soup(soup)
@@ -245,7 +246,11 @@ def fetch_order_from_url(self, url, update=True):
245246
_logger.warning('No url for order.')
246247
return None
247248

248-
order_details = self.read_order_from_url(url)
249+
try:
250+
order_details = self.read_order_from_url(url)
251+
except AttributeError: # reads from empty order site, does not find ceratin element
252+
return None
253+
249254
order = save_order(order_details, update=update)
250255

251256
# make log

utils.py

+19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from decimal import Decimal
22
import logging
33
import re
4+
from flask import Response, jsonify
45
from peewee import *
56
# from peewee import Expression # the building block for expressions
67

@@ -253,3 +254,21 @@ def get_orderlines_from_soup(soup):
253254
'total_vat_including': p_total_vat_including,
254255
})
255256
return orderlines
257+
258+
259+
def make_response(data, status=None, mimetype=None):
260+
if status is None:
261+
status = 200
262+
if mimetype is None:
263+
mimetype = 'application/json'
264+
return Response(data, status=status, mimetype=mimetype)
265+
266+
267+
def isdict(x):
268+
return isinstance(x, dict)
269+
270+
271+
def data_to_response(data):
272+
res = jsonify(**data) if isdict(data) else jsonify(data)
273+
res.status_code = 200
274+
return res

0 commit comments

Comments
 (0)