Commit 8fe0bbcd381cb883f9c885dff889481b10ab1e73
- runserver.py 2 -+
- swtstore/application.py 21 --+++++++++++++++++++
- swtstore/classes/exceptions.py 2 ++
- swtstore/classes/models/client.py 11 -----++++++
- swtstore/classes/models/context.py 2 -+
- swtstore/classes/models/sweet.py 75 --+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- swtstore/classes/models/um 2 -+
- swtstore/classes/utils/__init__.py 1 +
- swtstore/classes/utils/httputils.py 36 -----+++++++++++++++++++++++++++++++
- swtstore/classes/views/__init__.py 1 +
- swtstore/classes/views/api.py 154 -------------------------------------------------------------------------------------+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- swtstore/classes/views/app.py 21 -------------------++
- swtstore/classes/views/context.py 36 ---------------------------+++++++++
- swtstore/classes/views/frontend.py 12 ------++++++
- swtstore/classes/views/oauth.py 14 -------+++++++
- swtstore/classes/views/sweet.py 30 ++++++++++++++++++++++++++++++
- swtstore/classes/views/user.py 72 --------------++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- swtstore/static/js/main.js 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- swtstore/templates/index.html 2 -+
- swtstore/templates/layout.html 26 -----------------+++++++++
- swtstore/templates/my_apps.html 25 +++++++++++++++++++++++++
- swtstore/templates/my_contexts.html 20 ++++++++++++++++++++
- swtstore/templates/my_sweets.html 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- swtstore/templates/specific_sweet.html 28 ++++++++++++++++++++++++++++
- Diff rendering mode:
- inline
- side by side
runserver.py
19 | # Run the server if this script is directly executed | 19 | # Run the server if this script is directly executed |
---|---|---|---|
20 | # Presumably, this is development mode | 20 | # Presumably, this is development mode |
21 | if __name__ == '__main__': | 21 | if __name__ == '__main__': |
22 | 22 | app.run(host='0.0.0.0', port=5001) |
swtstore/application.py
5 | 5 | ||
---|---|---|---|
6 | from flask import Flask, request, jsonify, render_template, make_response, g | 6 | from flask import Flask, request, jsonify, render_template, make_response, g |
7 | import os | 7 | import os |
8 | import logging | ||
8 | 9 | ||
9 | from classes.database import db | 10 | from classes.database import db |
10 | from config import DefaultConfig | 11 | from config import DefaultConfig |
… | … | ||
22 | (views.api, '/api'), | 22 | (views.api, '/api'), |
23 | (views.user, '/users'), | 23 | (views.user, '/users'), |
24 | (views.context, '/contexts'), | 24 | (views.context, '/contexts'), |
25 | (views.sweet, '/sweets'), | ||
25 | (views.app, '/apps'), | 26 | (views.app, '/apps'), |
26 | (views.Oauth, '/oauth') | 27 | (views.Oauth, '/oauth') |
27 | ) | 28 | ) |
… | … | ||
150 | 150 | ||
151 | 151 | ||
152 | def configure_logging(app): | 152 | def configure_logging(app): |
153 | |||
154 | 153 | ||
154 | formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s ' | ||
155 | '[in %(pathname)s:%(lineno)d]') | ||
156 | |||
157 | # TODO: maybe we can use a RotatingFileHandler? | ||
158 | # Also error can be sent out via email. So we can also have a SMTPHandler? | ||
159 | log_handler = logging.StreamHandler() | ||
160 | |||
161 | if app.config.has_key('LOG_LEVEL'): | ||
162 | log_level = app.config['LOG_LEVEL'] or 'ERROR' | ||
163 | else: | ||
164 | log_level = 'ERROR' | ||
165 | |||
166 | log_handler.setLevel(log_level) | ||
167 | log_handler.setFormatter(formatter) | ||
168 | |||
169 | app.logger.addHandler(log_handler) |
swtstore/classes/exceptions.py
5 | 5 | ||
---|---|---|---|
6 | class AlreadyExistsError(Exception, DontWrapMixin): | 6 | class AlreadyExistsError(Exception, DontWrapMixin): |
7 | pass | 7 | pass |
8 | |||
9 | class InvalidPayload(Exception, DontWrapMixin): | ||
10 | pass |
swtstore/classes/models/client.py
3 | # class:: Client | 3 | # class:: Client |
---|---|---|---|
4 | 4 | ||
5 | from datetime import datetime, timedelta | 5 | from datetime import datetime, timedelta |
6 | from flask import current_app | ||
6 | 7 | ||
7 | from swtstore.classes.database import db | 8 | from swtstore.classes.database import db |
8 | from swtstore.classes.models.um import User | 9 | from swtstore.classes.models.um import User |
… | … | ||
157 | 157 | ||
158 | @oauth.clientgetter | 158 | @oauth.clientgetter |
159 | def loadClient(client_id): | 159 | def loadClient(client_id): |
160 | 160 | current_app.logger.debug('@oauth.clientgetter') | |
161 | #return Client.query.filter_by(id=client_id).first() | 161 | #return Client.query.filter_by(id=client_id).first() |
162 | return Client.query.get(client_id) | 162 | return Client.query.get(client_id) |
163 | 163 | ||
164 | @oauth.grantgetter | 164 | @oauth.grantgetter |
165 | def loadGrant(client_id, code): | 165 | def loadGrant(client_id, code): |
166 | 166 | current_app.logger.debug('@oauth.grantgetter') | |
167 | return Grant.query.filter_by(client_id=client_id, code=code).first() | 167 | return Grant.query.filter_by(client_id=client_id, code=code).first() |
168 | 168 | ||
169 | @oauth.grantsetter | 169 | @oauth.grantsetter |
170 | def saveGrant(client_id, code, request, *args, **kwargs): | 170 | def saveGrant(client_id, code, request, *args, **kwargs): |
171 | 171 | current_app.logger.debug('@oauth.grantsetter') | |
172 | expires = datetime.utcnow() + timedelta(seconds=100) | 172 | expires = datetime.utcnow() + timedelta(seconds=100) |
173 | grant = Grant( | 173 | grant = Grant( |
174 | client_id = client_id, | 174 | client_id = client_id, |
… | … | ||
184 | 184 | ||
185 | @oauth.tokengetter | 185 | @oauth.tokengetter |
186 | def loadToken(access_token=None, refresh_token=None): | 186 | def loadToken(access_token=None, refresh_token=None): |
187 | 187 | current_app.logger.debug('@oauth.tokengetter') | |
188 | if access_token: | 188 | if access_token: |
189 | return Token.query.filter_by(access_token=access_token).first() | 189 | return Token.query.filter_by(access_token=access_token).first() |
190 | elif refresh_token: | 190 | elif refresh_token: |
… | … | ||
192 | 192 | ||
193 | @oauth.tokensetter | 193 | @oauth.tokensetter |
194 | def saveToken(token, request, *args, **kwargs): | 194 | def saveToken(token, request, *args, **kwargs): |
195 | 195 | current_app.logger.debug('@oauth.tokensetter') | |
196 | 196 | ||
197 | toks = Token.query.filter_by(client_id=request.client.id, | 197 | toks = Token.query.filter_by(client_id=request.client.id, |
198 | user_id=request.user.id) | 198 | user_id=request.user.id) |
swtstore/classes/models/context.py
58 | 58 | ||
---|---|---|---|
59 | # return a context instance given a name | 59 | # return a context instance given a name |
60 | @staticmethod | 60 | @staticmethod |
61 | 61 | def getByName(name): | |
62 | return Context.query.filter_by(name=name).first() | 62 | return Context.query.filter_by(name=name).first() |
63 | 63 | ||
64 | @staticmethod | 64 | @staticmethod |
swtstore/classes/models/sweet.py
2 | # classes/sweet.py | 2 | # classes/sweet.py |
---|---|---|---|
3 | # class:: Sweet | 3 | # class:: Sweet |
4 | 4 | ||
5 | from flask import current_app | ||
5 | from datetime import datetime | 6 | from datetime import datetime |
6 | 7 | ||
7 | from swtstore.classes.database import db | 8 | from swtstore.classes.database import db |
8 | # custom SQLAlchemy type JSONType | 9 | # custom SQLAlchemy type JSONType |
9 | from swtstore.classes.models.types import JSONType | 10 | from swtstore.classes.models.types import JSONType |
10 | from swtstore.classes.utils import urlnorm # normalize URLs | 11 | from swtstore.classes.utils import urlnorm # normalize URLs |
12 | from swtstore.classes.models import Context | ||
13 | from swtstore.classes.models.um import User | ||
11 | 14 | ||
12 | class Sweet(db.Model): | 15 | class Sweet(db.Model): |
13 | """ customary docstring """ | 16 | """ customary docstring """ |
… | … | ||
33 | 33 | ||
34 | 34 | ||
35 | def __init__(self, who, what, where, how): | 35 | def __init__(self, who, what, where, how): |
36 | 36 | current_app.logger.info('initing sweet..') | |
37 | self.who = who | 37 | self.who = who |
38 | self.what = what | 38 | self.what = what |
39 | self.where = urlnorm(where) | 39 | self.where = urlnorm(where) |
… | … | ||
48 | return '[Sweet Object: <%s : @%s: #%s : %s>]' % (self.id, self.who, | 48 | return '[Sweet Object: <%s : @%s: #%s : %s>]' % (self.id, self.who, |
49 | self.what, self.where) | 49 | self.what, self.where) |
50 | 50 | ||
51 | # Update the sweet - only 'how' and 'where' fields can be updated | ||
52 | def update(self, **kwargs): | ||
53 | if kwargs.get('how'): | ||
54 | self.how = kwargs.get('how') | ||
55 | self.persist() | ||
56 | if kwargs.get('where'): | ||
57 | self.where = kwargs.get('where') | ||
58 | self.persist() | ||
59 | |||
60 | return None | ||
61 | |||
62 | |||
63 | # create multiple sweets from a list of JSON | ||
64 | @staticmethod | ||
65 | def createSweets(who, payload): | ||
66 | # the payload has to be a list; a list of swts | ||
67 | for each in payload: | ||
68 | if 'what' not in each and 'where' not in\ | ||
69 | each and 'how' not in each: | ||
70 | |||
71 | raise InvalidPayload('Invalid payload %s \n for creating\ | ||
72 | mutiple sweets' % (each)) | ||
73 | return None | ||
74 | |||
75 | # all ok. create swts from the list now | ||
76 | swts = [] | ||
77 | for each in payload: | ||
78 | |||
79 | what = Context.getByName(each['what']) | ||
80 | |||
81 | if what is None: | ||
82 | current_app.logger.info('Context "%s" do not exist. Aborting', | ||
83 | what) | ||
84 | g.error = 'Context do not exist' | ||
85 | abort(400) # this context doesn't exist! | ||
86 | |||
87 | current_app.logger.debug('SWEET PAYLOAD\n---\n%s\n%s\n%s\n%s\n----', | ||
88 | who, what, each['where'], each['how']) | ||
89 | |||
90 | new_sweet = Sweet(who, what, each['where'], each['how']) | ||
91 | |||
92 | new_sweet.persist() | ||
93 | current_app.logger.debug('New Sweet %s', new_sweet) | ||
94 | swts.append(new_sweet) | ||
95 | |||
96 | return swts | ||
97 | |||
98 | # get Sweets for frontend | ||
99 | @staticmethod | ||
100 | def getFrontendSwts(): | ||
101 | return Sweet.query.order_by(Sweet.created.desc()).all() | ||
102 | |||
103 | # get sweets all sweets authored by a particular user | ||
104 | @staticmethod | ||
105 | def getByCreator(user): | ||
106 | return Sweet.query.filter_by(who=user).\ | ||
107 | order_by(Sweet.created.desc()).all() | ||
108 | |||
109 | # allow to query all sweets based on "who", "what" and "where" params | ||
110 | @staticmethod | ||
111 | def queryByAll(params): | ||
112 | if params.get('who'): | ||
113 | params['who'] = User.getByName(params['who']) | ||
114 | if params.get('what'): | ||
115 | params['what'] = Context.getByName(params['what']) | ||
116 | |||
117 | return Sweet.query.filter_by(**params).all() | ||
118 | |||
51 | # return a dictionary of data members | 119 | # return a dictionary of data members |
52 | def to_dict(self): | 120 | def to_dict(self): |
53 | |||
54 | return { | 121 | return { |
55 | 'id': self.id, | 122 | 'id': self.id, |
56 | 'who': self.who.username, | 123 | 'who': self.who.username, |
… | … | ||
133 | # create and persist the sweet to the database | 133 | # create and persist the sweet to the database |
134 | def persist(self): | 134 | def persist(self): |
135 | 135 | ||
136 | current_app.logger.debug('Commiting sweet %s to db', self) | ||
136 | db.session.add(self) | 137 | db.session.add(self) |
137 | db.session.commit() | 138 | db.session.commit() |
swtstore/classes/utils/__init__.py
3 | __init__.py | 3 | __init__.py |
---|---|---|---|
4 | """ | 4 | """ |
5 | from urlnorm import urlnorm | 5 | from urlnorm import urlnorm |
6 | from httputils import makeCORSHeaders |
swtstore/classes/utils/httputils.py
1 | # HTTP utilities | ||
---|---|---|---|
2 | from flask import current_app | ||
1 | 3 | ||
2 | |||
3 | |||
4 | 4 | def makeCORSHeaders(response, host_url): | |
5 | current_app.logger.debug('makeCORSHeaders(): client\'s host_url: %s', | ||
6 | host_url) | ||
5 | response.headers['Access-Control-Allow-Origin'] = host_url | 7 | response.headers['Access-Control-Allow-Origin'] = host_url |
6 | response.headers['Access-Control-Max-Age'] = '3600' | 8 | response.headers['Access-Control-Max-Age'] = '3600' |
7 | response.headers['Access-Control-Allow-Credentials'] = 'true' | 9 | response.headers['Access-Control-Allow-Credentials'] = 'true' |
8 | response.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept' | 10 | response.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept' |
9 | 11 | ||
10 | |||
11 | 12 | current_app.logger.debug('Updated headers %s', response.headers) | |
12 | 13 | ||
13 | return response | 14 | return response |
15 | |||
16 | from datetime import timedelta | ||
17 | from flask import make_response, request, current_app | ||
18 | from functools import update_wrapper | ||
19 | |||
20 | """ | ||
21 | def crossdomain(origin=None, methods=None, headers=None, max_age=3600, | ||
22 | attach_to_all=True, automatic_options=True): | ||
23 | |||
24 | if methods is not None: | ||
25 | methods = ', '.join(sorted(i.upper() for i in methods)) | ||
26 | if headers in not None and not isinstance(headers, basestring): | ||
27 | headers = ', '.join(i.upper() for i in headers) | ||
28 | if not isinstance(origin, basestring): | ||
29 | origin = ', '.join(origin) | ||
30 | if isinstance(max_age, timedelta): | ||
31 | max_age = max_age.total_seconds() | ||
32 | |||
33 | def get_methods(): | ||
34 | if methods is not None: | ||
35 | return methods | ||
36 | |||
37 | options_resp = current | ||
38 | |||
39 | """ |
swtstore/classes/views/__init__.py
2 | from .api import api | 2 | from .api import api |
---|---|---|---|
3 | from .user import user | 3 | from .user import user |
4 | from .context import context | 4 | from .context import context |
5 | from .sweet import sweet | ||
5 | from .app import app | 6 | from .app import app |
6 | from .oauth import Oauth | 7 | from .oauth import Oauth |
swtstore/classes/views/api.py
1 | |||
---|---|---|---|
2 | |||
3 | 1 | from flask import Module, jsonify, request, make_response, abort, g, json,\ | |
2 | current_app | ||
4 | 3 | ||
5 | from swtstore.classes.models import Context | 4 | from swtstore.classes.models import Context |
6 | from swtstore.classes.models import Sweet | 5 | from swtstore.classes.models import Sweet |
7 | 6 | from swtstore.classes.exceptions import AlreadyExistsError, InvalidPayload | |
8 | from swtstore.classes.utils import urlnorm # normalize URLs | 7 | from swtstore.classes.utils import urlnorm # normalize URLs |
9 | 8 | from swtstore.classes.utils.httputils import makeCORSHeaders | |
10 | from swtstore.classes import oauth | 9 | from swtstore.classes import oauth |
11 | 10 | ||
12 | 11 | ||
… | … | ||
13 | 13 | ||
14 | 14 | ||
15 | # Get a specific sweet | 15 | # Get a specific sweet |
16 | 16 | # Update a specific sweet | |
17 | @api.route('/sweets/<int:id>', methods=['GET', 'PUT']) | ||
17 | def getSweetById(id): | 18 | def getSweetById(id): |
18 | 19 | ||
20 | # Get a specific sweet | ||
21 | if request.method == 'GET': | ||
19 | sweet = Sweet.query.get(id) | 22 | sweet = Sweet.query.get(id) |
20 | |||
21 | |||
22 | 23 | ||
23 | |||
24 | 24 | if sweet is None: | |
25 | abort(404) | ||
25 | 26 | ||
26 | |||
27 | 27 | current_app.logger.debug('getSweetById: %s', sweet) | |
28 | return jsonify(sweet.to_dict()) | ||
28 | 29 | ||
30 | # Update a specific sweet | ||
31 | elif request.method == 'PUT': | ||
32 | payload = request.json | ||
33 | if payload is None: | ||
34 | abort(400) | ||
29 | 35 | ||
36 | current_app.logger.debug('Update Sweet: recvd payload: %s', payload) | ||
37 | |||
38 | sweet = Sweet.query.get(id) | ||
39 | |||
40 | if sweet is None: | ||
41 | abort(404) | ||
42 | |||
43 | current_app.logger.debug('Updating sweet %s with new how: %s ', | ||
44 | sweet.id, payload) | ||
45 | |||
46 | sweet.update(how=payload) | ||
47 | |||
48 | return jsonify(sweet.to_dict()) | ||
49 | |||
50 | |||
30 | # Post a sweet to the sweet store | 51 | # Post a sweet to the sweet store |
31 | @api.route('/sweets', methods=['OPTIONS', 'POST']) | 52 | @api.route('/sweets', methods=['OPTIONS', 'POST']) |
32 | @oauth.require_oauth('email', 'sweet') | 53 | @oauth.require_oauth('email', 'sweet') |
… | … | ||
57 | 57 | ||
58 | client = oauth_request.client | 58 | client = oauth_request.client |
59 | 59 | ||
60 | |||
61 | 60 | #TODO: make a decorator of CORS request | |
61 | response = makeCORSHeaders(response, client.host_url) | ||
62 | 62 | ||
63 | if request.method == 'OPTIONS': | 63 | if request.method == 'OPTIONS': |
64 | response.status_code = 200 | 64 | response.status_code = 200 |
65 | return response | 65 | return response |
66 | 66 | ||
67 | |||
68 | |||
69 | |||
70 | |||
71 | |||
72 | 67 | payload = request.json or request.data | |
68 | if not payload: | ||
69 | current_app.logger.error('data not found in payload!') | ||
73 | g.error= 'data not found in payload!' | 70 | g.error= 'data not found in payload!' |
74 | abort(400) | 71 | abort(400) |
75 | 72 | ||
76 | |||
77 | 73 | current_app.logger.debug('new sweet payload recvd.. %s', payload) | |
78 | 74 | ||
79 | |||
80 | |||
81 | |||
82 | 75 | # Get the authenticated user from the oauth request object. | |
76 | # Older swtr clients sending `who` in string will be ignored. | ||
77 | who = oauth_request.user | ||
83 | 78 | ||
84 | |||
85 | 79 | try: | |
80 | swts = Sweet.createSweets(who, payload) | ||
81 | except InvalidPayload(msg): | ||
82 | current_app.logger.error('Invalid Payload in request') | ||
83 | abort(400) | ||
86 | 84 | ||
87 | |||
88 | |||
89 | |||
90 | |||
91 | |||
92 | |||
93 | |||
94 | |||
95 | |||
96 | |||
97 | |||
98 | |||
99 | |||
100 | |||
101 | |||
102 | |||
103 | |||
104 | |||
105 | |||
106 | |||
107 | |||
108 | |||
109 | |||
110 | |||
111 | |||
112 | |||
113 | |||
114 | |||
115 | |||
116 | |||
117 | response.status_code = 200 | 85 | response.status_code = 200 |
118 | 86 | response.headers['Content-type'] = 'application/json' | |
87 | response.data = json.dumps([i.to_dict() for i in swts]) | ||
119 | return response | 88 | return response |
120 | 89 | ||
121 | 90 | ||
122 | # The Sweet query API: /sweets/q?who=<>&what=<>&where=<> | 91 | # The Sweet query API: /sweets/q?who=<>&what=<>&where=<> |
123 | # args: who, what, where | 92 | # args: who, what, where |
124 | @api.route('/sweets/q', methods=['GET', 'OPTIONS']) | 93 | @api.route('/sweets/q', methods=['GET', 'OPTIONS']) |
125 | |||
126 | 94 | #@oauth.require_oauth('sweet') | |
95 | def querySweets(): | ||
127 | 96 | ||
128 | response = make_response() | 97 | response = make_response() |
129 | |||
130 | 98 | origin = request.headers.get('Origin', '*') | |
99 | response = makeCORSHeaders(response, origin) | ||
131 | 100 | ||
132 | if request.method == 'OPTIONS': | 101 | if request.method == 'OPTIONS': |
133 | reponse.status_code = 200 | 102 | reponse.status_code = 200 |
… | … | ||
112 | if args.get('who'): | 112 | if args.get('who'): |
113 | params['who'] = args.get('who') | 113 | params['who'] = args.get('who') |
114 | if args.get('what'): | 114 | if args.get('what'): |
115 | |||
116 | 115 | params['what'] = args.get('what') | |
117 | if args.get('where'): | 116 | if args.get('where'): |
118 | params['where'] = urlnorm(args.get('where')) | 117 | params['where'] = urlnorm(args.get('where')) |
119 | 118 | ||
… | … | ||
120 | if len(params) == 0: | 120 | if len(params) == 0: |
121 | abort(400) | 121 | abort(400) |
122 | 122 | ||
123 | |||
124 | 123 | current_app.logger.debug('recvd params: %s', params) | |
125 | 124 | ||
125 | sweets = Sweet.queryByAll(params) | ||
126 | 126 | ||
127 | |||
128 | |||
129 | if len(sweets) == 0: | 127 | if len(sweets) == 0: |
130 | 128 | current_app.logger.info('No sweets found to satisfy query..') | |
131 | abort(404) | 129 | abort(404) |
132 | 130 | ||
133 | swts = [i.to_dict() for i in sweets] | 131 | swts = [i.to_dict() for i in sweets] |
… | … | ||
139 | @api.route('/contexts/<name>', methods=['GET']) | 139 | @api.route('/contexts/<name>', methods=['GET']) |
140 | def getContextByName(name): | 140 | def getContextByName(name): |
141 | 141 | ||
142 | 142 | context = Context.getByName(name) | |
143 | if context is None: | 143 | if context is None: |
144 | abort(404) | 144 | abort(404) |
145 | 145 | ||
146 | 146 | current_app.logger.debug('getContextByName : %s', context) | |
147 | return jsonify(context.to_dict()) | 147 | return jsonify(context.to_dict()) |
148 | 148 | ||
149 | # Get a specific context with its definition; based on id | 149 | # Get a specific context with its definition; based on id |
… | … | ||
154 | if context is None: | 154 | if context is None: |
155 | abort(404) | 155 | abort(404) |
156 | 156 | ||
157 | |||
158 | |||
159 | |||
160 | 157 | current_app.logger.debug('getContextById response: %s', | |
158 | jsonify(context.to_dict()).data) | ||
159 | |||
161 | return jsonify(context.to_dict()) | 160 | return jsonify(context.to_dict()) |
162 | 161 | ||
163 | 162 | ||
… | … | ||
176 | # if not found send back a 400 | 176 | # if not found send back a 400 |
177 | abort(400) | 177 | abort(400) |
178 | 178 | ||
179 | |||
180 | |||
181 | 179 | current_app.logger.debug('new context payload recvd.. %s', payload) | |
182 | 180 | ||
183 | # if data is invalid send back 400 | 181 | # if data is invalid send back 400 |
184 | if 'name' not in payload and 'definition' not in payload: | 182 | if 'name' not in payload and 'definition' not in payload: |
… | … | ||
187 | 187 | ||
188 | except AlreadyExistsError: | 188 | except AlreadyExistsError: |
189 | # context with same name exists; send back 400? | 189 | # context with same name exists; send back 400? |
190 | 190 | current_app.logger.info('Context Already Exists Error') | |
191 | abort(400) | 191 | abort(400) |
192 | 192 | ||
193 | |||
194 | 193 | current_app.logger.debug('new context created: %s', new_context) | |
194 | |||
195 | # all ok. save the new context | 195 | # all ok. save the new context |
196 | res = new_context.persist() | 196 | res = new_context.persist() |
197 | 197 | ||
… | … | ||
204 | @oauth.require_oauth('email') | 204 | @oauth.require_oauth('email') |
205 | def getCurrentUser(oauth_request): | 205 | def getCurrentUser(oauth_request): |
206 | response = make_response() | 206 | response = make_response() |
207 | |||
208 | 207 | response = makeCORSHeaders(response, oauth_request.client.host_url) | |
209 | response.status_code = 200 | 208 | response.status_code = 200 |
210 | 209 | ||
211 | if request.method == 'OPTIONS': | 210 | if request.method == 'OPTIONS': |
212 | return response | 211 | return response |
213 | 212 | ||
213 | response.headers['Content-type'] = 'application/json' | ||
214 | # We have the user object along with the oauth request. Just return it back | ||
214 | response.data = json.dumps(oauth_request.user.to_dict()) | 215 | response.data = json.dumps(oauth_request.user.to_dict()) |
215 | return response | 216 | return response |
swtstore/classes/views/app.py
1 | |||
---|---|---|---|
2 | # -*- coding utf-8 -*- | 1 | # -*- coding utf-8 -*- |
3 | # classes/views/apps.py | 2 | # classes/views/apps.py |
4 | 3 | ||
5 | |||
6 | from flask import Module, jsonify, request, render_template, redirect,\ | 4 | from flask import Module, jsonify, request, render_template, redirect,\ |
7 | url_for, flash, abort | 5 | url_for, flash, abort |
8 | 6 | ||
… | … | ||
14 | 14 | ||
15 | app = Module(__name__) | 15 | app = Module(__name__) |
16 | 16 | ||
17 | |||
18 | |||
19 | |||
20 | |||
21 | |||
22 | |||
23 | 17 | ||
24 | |||
25 | |||
26 | |||
27 | |||
28 | |||
29 | |||
30 | |||
31 | @app.route('/register', methods=['GET', 'POST']) | 18 | @app.route('/register', methods=['GET', 'POST']) |
32 | def register(): | 19 | def register(): |
33 | current_user = User.getCurrentUser() | 20 | current_user = User.getCurrentUser() |
34 | if current_user is None: | 21 | if current_user is None: |
35 | return redirect(url_for('frontend.index')) | 22 | return redirect(url_for('frontend.index')) |
36 | 23 | ||
37 | |||
38 | |||
39 | if request.method == 'GET': | 24 | if request.method == 'GET': |
40 | 25 | return render_template('register_app.html') | |
41 | 26 | ||
42 | elif request.method == 'POST': | 27 | elif request.method == 'POST': |
43 | req_fields = ['name', 'host_url', 'redirect_uris', 'scopes'] | 28 | req_fields = ['name', 'host_url', 'redirect_uris', 'scopes'] |
… | … | ||
43 | ) | 43 | ) |
44 | new_app.persist() | 44 | new_app.persist() |
45 | 45 | ||
46 | 46 | return redirect(url_for('user.myApps')) |
swtstore/classes/views/context.py
1 | |||
---|---|---|---|
2 | # -*- coding utf-8 -*- | 1 | # -*- coding utf-8 -*- |
3 | # classes/views/context.py | 2 | # classes/views/context.py |
4 | 3 | ||
4 | from flask import Module, jsonify, request, render_template, redirect,\ | ||
5 | url_for, json, current_app | ||
5 | 6 | ||
6 | |||
7 | |||
8 | |||
9 | from swtstore.classes.models import Context | 7 | from swtstore.classes.models import Context |
10 | from swtstore.classes.models.um import User | 8 | from swtstore.classes.models.um import User |
11 | 9 | ||
12 | 10 | ||
13 | context = Module(__name__) | 11 | context = Module(__name__) |
14 | 12 | ||
15 | |||
16 | |||
17 | |||
18 | |||
19 | |||
20 | |||
21 | |||
22 | |||
23 | |||
24 | |||
25 | |||
26 | |||
27 | |||
28 | |||
29 | |||
30 | @context.route('/register', methods=['GET', 'POST']) | 13 | @context.route('/register', methods=['GET', 'POST']) |
31 | def register(): | 14 | def register(): |
32 | current_user = User.getCurrentUser() | 15 | current_user = User.getCurrentUser() |
33 | if current_user is None: | 16 | if current_user is None: |
34 | 17 | return redirect(url_for('frontend.index')) | |
35 | 18 | ||
36 | |||
37 | |||
38 | if request.method == 'GET': | 19 | if request.method == 'GET': |
39 | 20 | return render_template('register_context.html') | |
40 | 21 | ||
41 | if request.method == 'POST': | 22 | if request.method == 'POST': |
42 | if not request.form.get('name') or not request.form.get('defn'): | 23 | if not request.form.get('name') or not request.form.get('defn'): |
43 | abort(400) | 24 | abort(400) |
44 | 25 | ||
45 | 26 | current_app.logger.debug('New Context: defn: %s ', | |
27 | request.form.get('defn')) | ||
46 | json_ld = json.loads(request.form.get('defn')) | 28 | json_ld = json.loads(request.form.get('defn')) |
47 | 29 | current_app.logger.debug('Resulting json_ld %s', json_ld) | |
48 | 30 | ||
49 | new_context = Context( | 31 | new_context = Context( |
50 | name = request.form.get('name'), | 32 | name = request.form.get('name'), |
51 | definition = json_ld, | 33 | definition = json_ld, |
52 | user_id = current_user.id | 34 | user_id = current_user.id |
53 | ) | 35 | ) |
54 | 36 | current_app.logger.debug('New Context created: %s', new_context) | |
55 | new_context.persist() | 37 | new_context.persist() |
56 | 38 | ||
57 | 39 | return redirect(url_for('user.myContexts')) |
swtstore/classes/views/frontend.py
2 | # classes/views/frontend.py | 2 | # classes/views/frontend.py |
---|---|---|---|
3 | 3 | ||
4 | 4 | ||
5 | 5 | from flask import Module, jsonify, request, render_template, redirect,\ | |
6 | url_for, g, current_app | ||
6 | 7 | ||
8 | from sqlalchemy import desc | ||
9 | |||
7 | from swtstore.classes.models import Sweet | 10 | from swtstore.classes.models import Sweet |
8 | from swtstore.classes.models.um import User | 11 | from swtstore.classes.models.um import User |
9 | 12 | ||
… | … | ||
15 | 15 | ||
16 | @frontend.route('/', methods=['GET']) | 16 | @frontend.route('/', methods=['GET']) |
17 | def index(): | 17 | def index(): |
18 | |||
19 | 18 | sweets = Sweet.getFrontendSwts() | |
20 | 19 | ||
21 | user = User.getCurrentUser() | 20 | user = User.getCurrentUser() |
22 | |||
23 | |||
24 | 21 | ||
25 | 22 | return render_template('index.html', sweets=sweets) | |
26 | 23 | ||
27 | 24 | ||
28 | # Create a new sweet context | 25 | # Create a new sweet context |
swtstore/classes/views/oauth.py
1 | # -*- coding utf-8 -*- | 1 | # -*- coding utf-8 -*- |
---|---|---|---|
2 | # classes/views/oauth.py | 2 | # classes/views/oauth.py |
3 | 3 | ||
4 | |||
5 | 4 | from flask import Module, jsonify, request, render_template, redirect,\ | |
5 | url_for, current_app | ||
6 | 6 | ||
7 | from swtstore.classes import oauth | 7 | from swtstore.classes import oauth |
8 | from swtstore.classes.models.um import User | 8 | from swtstore.classes.models.um import User |
… | … | ||
21 | if request.method == 'GET': | 21 | if request.method == 'GET': |
22 | client_id = kwargs.get('client_id') | 22 | client_id = kwargs.get('client_id') |
23 | client = Client.query.get(client_id) | 23 | client = Client.query.get(client_id) |
24 | |||
25 | 24 | current_app.logger.debug('In /authorize: client: %s', client) | |
26 | kwargs['client'] = client | 25 | kwargs['client'] = client |
27 | kwargs['user'] = current_user | 26 | kwargs['user'] = current_user |
28 | 27 | current_app.logger.debug('kwargs %s', kwargs) | |
29 | return render_template('authorize.html', **kwargs) | 28 | return render_template('authorize.html', **kwargs) |
30 | 29 | ||
31 | confirm = request.form.get('confirm', 'no') | 30 | confirm = request.form.get('confirm', 'no') |
32 | 31 | current_app.logger.debug('confirm authorize from user: %s', confirm) | |
33 | return confirm == 'yes' | 32 | return confirm == 'yes' |
34 | 33 | ||
35 | @Oauth.route('/token', methods=['GET', 'POST']) | 34 | @Oauth.route('/token', methods=['GET', 'POST']) |
36 | @oauth.token_handler | 35 | @oauth.token_handler |
37 | def access_token(): | 36 | def access_token(): |
38 | #print request.form | 37 | #print request.form |
39 | 38 | current_app.logger.debug('access token touched..') | |
40 | return None | 39 | return None |
41 | 40 | ||
42 | @Oauth.route('/errors') | 41 | @Oauth.route('/errors') |
swtstore/classes/views/sweet.py
1 | # -*- coding utf-8 -*- | ||
---|---|---|---|
2 | # classes/views/sweet.py | ||
3 | |||
4 | |||
5 | from flask import Module, jsonify, request, render_template, redirect,\ | ||
6 | url_for, abort, json | ||
7 | |||
8 | from swtstore.classes.models import Context | ||
9 | from swtstore.classes.models import Sweet | ||
10 | from swtstore.classes.models.um import User | ||
11 | |||
12 | |||
13 | sweet = Module(__name__) | ||
14 | |||
15 | @sweet.route('/<id>', methods=['GET']) | ||
16 | def showSweet(id): | ||
17 | #current_user = User.getCurrentUser() | ||
18 | #if current_user is None: | ||
19 | # return redirect(url_for('index')) | ||
20 | |||
21 | #user = current_user.to_dict() | ||
22 | print "recvd sweet id: %s" % (id) | ||
23 | sweet = Sweet.query.get(id) | ||
24 | if sweet: | ||
25 | print "sweet found " + str(sweet) | ||
26 | return render_template('specific_sweet.html', sweet=sweet) | ||
27 | else: | ||
28 | abort(404) |
swtstore/classes/views/user.py
3 | 3 | ||
---|---|---|---|
4 | import requests | 4 | import requests |
5 | 5 | ||
6 | # flask imports | ||
6 | from flask import Module, jsonify, request, render_template, session,\ | 7 | from flask import Module, jsonify, request, render_template, session,\ |
7 | |||
8 | 8 | make_response, url_for, redirect, json, current_app | |
9 | 9 | ||
10 | # swtstore imports | ||
10 | from swtstore.classes.models.um import User | 11 | from swtstore.classes.models.um import User |
12 | from swtstore.classes.models import Sweet, Context, Client | ||
11 | 13 | ||
12 | 14 | from swtstore.classes.utils.httputils import makeCORSHeaders | |
13 | from swtstore.config import DefaultConfig | 15 | from swtstore.config import DefaultConfig |
14 | 16 | ||
15 | 17 | ||
… | … | ||
23 | def login(): | 23 | def login(): |
24 | 24 | ||
25 | response = make_response() | 25 | response = make_response() |
26 | 26 | #response = makeCORSHeaders(response) | |
27 | 27 | ||
28 | if 'assertion' not in request.form: | 28 | if 'assertion' not in request.form: |
29 | response.status_code = 400 | 29 | response.status_code = 400 |
30 | return response | 30 | return response |
31 | 31 | ||
32 | 32 | current_app.logger.debug('remote address of request for user login %s', | |
33 | request.remote_addr) | ||
34 | |||
33 | data = {'assertion': request.form['assertion'], 'audience': | 35 | data = {'assertion': request.form['assertion'], 'audience': |
34 | config.SWTSTORE_URL} | 36 | config.SWTSTORE_URL} |
35 | 37 | ||
36 | resp = requests.post(config.MOZ_PERSONA_VERIFIER, data=data, verify=True) | 38 | resp = requests.post(config.MOZ_PERSONA_VERIFIER, data=data, verify=True) |
37 | |||
38 | 39 | current_app.logger.debug('Response code from MOZ_PERSONA_VERIFIER %s', | |
40 | resp.status_code) | ||
41 | current_app.logger.debug('Response body: %s', resp.json()) | ||
39 | 42 | ||
40 | if resp.ok: | 43 | if resp.ok: |
41 | verified_data = json.loads(resp.content) | 44 | verified_data = json.loads(resp.content) |
… | … | ||
48 | current_user = User.query.filter_by(email=user_email).first() | 48 | current_user = User.query.filter_by(email=user_email).first() |
49 | # user doesn't exist; create her | 49 | # user doesn't exist; create her |
50 | if current_user is None: | 50 | if current_user is None: |
51 | |||
52 | 51 | current_app.logger.info('user with email %s doesn\'t exist', | |
52 | user_email) | ||
53 | current_app.logger.info('creating new user: %s', user_email) | ||
54 | |||
53 | new_user = User('', user_email) | 55 | new_user = User('', user_email) |
54 | new_user.persist() | 56 | new_user.persist() |
55 | current_user = new_user | 57 | current_user = new_user |
56 | 58 | ||
57 | #session.update({'email': verified_data['email']}) | 59 | #session.update({'email': verified_data['email']}) |
58 | 60 | current_app.logger.info('logging in user with email %s', | |
61 | user_email) | ||
59 | session['email'] = current_user.email | 62 | session['email'] = current_user.email |
60 | 63 | ||
61 | response.status_code = 200 | 64 | response.status_code = 200 |
… | … | ||
72 | def logout(): | 72 | def logout(): |
73 | 73 | ||
74 | response = make_response() | 74 | response = make_response() |
75 | 75 | #response = makeCORSHeaders(response) | |
76 | 76 | ||
77 | if 'email' in session: | 77 | if 'email' in session: |
78 | |||
79 | 78 | current_app.logger.info('logging out user %s', session['email']) | |
80 | session.pop('email') | 79 | session.pop('email') |
81 | 80 | ||
82 | response.status_code = 200 | 81 | response.status_code = 200 |
… | … | ||
92 | return render_template('me.html', user=current_user) | 92 | return render_template('me.html', user=current_user) |
93 | 93 | ||
94 | username = request.form.get('username') | 94 | username = request.form.get('username') |
95 | 95 | ||
96 | current_app.logger.debug('Updating username of %s to %s', | ||
97 | current_user.username, username) | ||
98 | |||
96 | current_user.update(username=username) | 99 | current_user.update(username=username) |
97 | 100 | ||
98 | return redirect(url_for('profile')) | 101 | return redirect(url_for('profile')) |
102 | |||
103 | |||
104 | @user.route('/me/sweets', methods=['GET']) | ||
105 | def mySweets(): | ||
106 | |||
107 | user = User.getCurrentUser() | ||
108 | if user is None: | ||
109 | return redirect(url_for('frontend.index')) | ||
110 | |||
111 | swts = Sweet.getByCreator(user) | ||
112 | return render_template('my_sweets.html', sweets=swts) | ||
113 | |||
114 | |||
115 | @user.route('/me/contexts', methods=['GET']) | ||
116 | def myContexts(): | ||
117 | |||
118 | user = User.getCurrentUser() | ||
119 | if user is None: | ||
120 | return redirect(url_for('frontend.index')) | ||
121 | |||
122 | contexts = Context.getByCreator(user.id) | ||
123 | return render_template('my_contexts.html', contexts=contexts) | ||
124 | |||
125 | |||
126 | @user.route('/me/apps', methods=['GET']) | ||
127 | def myApps(): | ||
128 | |||
129 | # make a decorator out of this repetative code | ||
130 | user = User.getCurrentUser() | ||
131 | if user is None: | ||
132 | return redirect(url_for('frontend.index')) | ||
133 | |||
134 | apps = Client.getClientsByCreator(user.id) | ||
135 | return render_template('my_apps.html', apps=apps) |
swtstore/static/js/main.js
5 | this.attachLogout(); | 5 | this.attachLogout(); |
---|---|---|---|
6 | this.initPersona(); | 6 | this.initPersona(); |
7 | this.activeNav(); | 7 | this.activeNav(); |
8 | $('.edit-sweet').click(ss.editSweet); | ||
8 | }; | 9 | }; |
9 | 10 | ||
10 | ss.activeNav = function() { | 11 | ss.activeNav = function() { |
… | … | ||
69 | }); | 69 | }); |
70 | } | 70 | } |
71 | }); | 71 | }); |
72 | }; | ||
73 | |||
74 | ss.editSweet = function(event) { | ||
75 | var target = $(event.currentTarget).attr('for'); | ||
76 | var how = JSON.parse($(event.currentTarget).siblings('.how').html()); | ||
77 | //console.log(how); | ||
78 | // update sweet function | ||
79 | function updateSweet(event) { | ||
80 | var changed = false; | ||
81 | for(var field in how) { | ||
82 | var data = $('#edit-sweet-modal .modal-body textarea[name="'+field+'"]').val(); | ||
83 | var item = (typeof how[field] === 'object') ? JSON.stringify(how[field]) : | ||
84 | how[field]; | ||
85 | if(data !== item) { | ||
86 | changed = true; | ||
87 | how[field] = data; | ||
88 | console.log('Updated '+ field + ' with data: ', data); | ||
89 | } | ||
90 | } | ||
91 | if(changed) { | ||
92 | $('#save-edited-sweet').text('Saving Changes'); | ||
93 | |||
94 | $.ajax({ | ||
95 | type: 'PUT', | ||
96 | url: 'http://localhost:5001/api/sweets/'+target, | ||
97 | contentType: 'application/json', | ||
98 | data: JSON.stringify(how), | ||
99 | success: function(data) { | ||
100 | console.log('Updated swt from the server ', data); | ||
101 | $('#save-edited-sweet').text('Save Changes'); | ||
102 | $('#edit-sweet-modal').modal('hide'); | ||
103 | }, | ||
104 | error: function() { | ||
105 | $('#save-edited-sweet').text('Save Changes'); | ||
106 | } | ||
107 | }); | ||
108 | } | ||
109 | else { | ||
110 | return; | ||
111 | } | ||
112 | } | ||
113 | // prepare the edit view | ||
114 | $('#edit-sweet-modal .modal-body').html(''); | ||
115 | for(var field in how) { | ||
116 | var item = (typeof how[field] === 'object') ? JSON.stringify(how[field]) : | ||
117 | how[field]; | ||
118 | |||
119 | $('#edit-sweet-modal .modal-body').append('<div class="form-group"> <b>'+ | ||
120 | field+'</b>'); | ||
121 | /*$('<input>', | ||
122 | {name: field, value: item, class: 'form-control', type: 'text'}). | ||
123 | appendTo('#edit-sweet-modal .modal-body');*/ | ||
124 | $('#edit-sweet-modal .modal-body').append('<textarea name="'+field+'" class="form-control">'+item+'</textarea>'); | ||
125 | |||
126 | $('#edit-sweet-modal').append('</div>'); | ||
127 | } | ||
128 | // launch the modal | ||
129 | $('#edit-sweet-modal').modal(); | ||
130 | |||
131 | // attach event handlers | ||
132 | $('#save-edited-sweet').off('click'); | ||
133 | $('#save-edited-sweet').on('click', updateSweet); | ||
134 | |||
72 | }; | 135 | }; |
73 | 136 | ||
74 | })(ss); | 137 | })(ss); |
swtstore/templates/index.html
28 | <small><i>created: {{sweet.created }}</i></small> | 28 | <small><i>created: {{sweet.created }}</i></small> |
---|---|---|---|
29 | 29 | ||
30 | <span class="pull-right permalink"> | 30 | <span class="pull-right permalink"> |
31 | 31 | <a href="{{ url_for('sweet.showSweet', id=sweet.id) }}"> | |
32 | <i class="glyphicon glyphicon-share"></i> | 32 | <i class="glyphicon glyphicon-share"></i> |
33 | </a> | 33 | </a> |
34 | </li> | 34 | </li> |
swtstore/templates/layout.html
17 | <li> | 17 | <li> |
---|---|---|---|
18 | <a href="{{ url_for('frontend.index') }}"> Home </a> | 18 | <a href="{{ url_for('frontend.index') }}"> Home </a> |
19 | </li> | 19 | </li> |
20 | 20 | {% if not session.email %} | |
21 | <li> | 21 | <li> |
22 | <a id="login" href="#"> Login </a> | 22 | <a id="login" href="#"> Login </a> |
23 | </li> | 23 | </li> |
24 | {% else %} | 24 | {% else %} |
25 | <!-- swts list --> | ||
26 | <li> | ||
27 | <a href="{{ url_for('user.mySweets') }}"> My Sweets </a> | ||
28 | </li> | ||
25 | <!-- the cog menu --> | 29 | <!-- the cog menu --> |
26 | <li class="dropdown user-menu"> | 30 | <li class="dropdown user-menu"> |
27 | <a class="dropdown-toggle" data-toggle="dropdown" href="#"> | 31 | <a class="dropdown-toggle" data-toggle="dropdown" href="#"> |
… | … | ||
33 | </a> | 33 | </a> |
34 | <ul class="dropdown-menu" role="menu"> | 34 | <ul class="dropdown-menu" role="menu"> |
35 | <li> <a href="{{ url_for('app.register') }}">Register New App</a> </li> | 35 | <li> <a href="{{ url_for('app.register') }}">Register New App</a> </li> |
36 | 36 | <li> <a href="{{ url_for('user.myApps') }}">Registered Apps</a> </li> | |
37 | <li class="divider"></li> | 37 | <li class="divider"></li> |
38 | <li> <a href="{{ url_for('context.register') }}">Register New Context</a> </li> | 38 | <li> <a href="{{ url_for('context.register') }}">Register New Context</a> </li> |
39 | 39 | <li> <a href="{{ url_for('user.myContexts') }}">Registered Contexts</a> </li> | |
40 | </ul> | 40 | </ul> |
41 | </li> | 41 | </li> |
42 | <!-- the user menu --> | 42 | <!-- the user menu --> |
… | … | ||
48 | <ul class="dropdown-menu" role="menu"> | 48 | <ul class="dropdown-menu" role="menu"> |
49 | <li> | 49 | <li> |
50 | <a href="{{ url_for('user.profile') }}"><small> | 50 | <a href="{{ url_for('user.profile') }}"><small> |
51 | |||
52 | |||
53 | |||
54 | |||
55 | 51 | {{ session.email }} | |
56 | </small></a> | 52 | </small></a> |
57 | </li> | 53 | </li> |
58 | <li class="divider"></li> | 54 | <li class="divider"></li> |
… | … | ||
85 | ss.loginURL = function() { return "{{ url_for('user.login') }}"; }; | 85 | ss.loginURL = function() { return "{{ url_for('user.login') }}"; }; |
86 | ss.logoutURL = function() { return "{{ url_for('user.logout') }}"; }; | 86 | ss.logoutURL = function() { return "{{ url_for('user.logout') }}"; }; |
87 | ss.loggedInUser = function() { | 87 | ss.loggedInUser = function() { |
88 | |||
89 | |||
90 | |||
91 | |||
92 | |||
93 | |||
94 | |||
95 | |||
96 | 88 | return ("{{ session.email }}") ? "{{ session.email }}" : null; | |
97 | }; | 89 | }; |
98 | window.onload = function() { | 90 | window.onload = function() { |
99 | ss.init(); | 91 | ss.init(); |
swtstore/templates/my_apps.html
1 | {% extends "layout.html" %} | ||
---|---|---|---|
2 | {% block body %} | ||
3 | <div> | ||
4 | {% if apps|length > 0 %} | ||
5 | <ul> | ||
6 | {% for app in apps %} | ||
7 | <div class="well"> | ||
8 | <div class="pull-right"> | ||
9 | <span class="glyphicon glyphicon-trash"></span> | ||
10 | </div> | ||
11 | <div><h4> {{ app.name }} </h4></div> | ||
12 | <div><h5> {{ app.description }} </h5></div> | ||
13 | <div>APP ID: {{ app.id }} </div> | ||
14 | <div> APP Secret: {{ app.client_secret }} </div> | ||
15 | <div>Host URL: {{ app.host_url }} </div> | ||
16 | <div>Redirect URL: {{ app.redirect_uris|join(', ') }} </div> | ||
17 | <div>Scopes: {{ app.default_scopes|join(', ') }} </div> | ||
18 | </div> | ||
19 | {% endfor %} | ||
20 | </ul> | ||
21 | {% else %} | ||
22 | <em> You haven't registered any apps yet! </em> | ||
23 | {% endif %} | ||
24 | </div> | ||
25 | {% endblock %} |
swtstore/templates/my_contexts.html
1 | {% extends "layout.html" %} | ||
---|---|---|---|
2 | {% block body %} | ||
3 | <div> | ||
4 | {% if contexts|length > 0 %} | ||
5 | <ul> | ||
6 | {% for context in contexts %} | ||
7 | <div class="well"> | ||
8 | <div class="pull-right"> | ||
9 | <span class="glyphicon glyphicon-trash"></span> | ||
10 | </div> | ||
11 | <div> {{ context.name }} </div> | ||
12 | <div> {{ context.definition }} </div> | ||
13 | </div> | ||
14 | {% endfor %} | ||
15 | </ul> | ||
16 | {% else %} | ||
17 | <em> You haven't registered any contexts yet! </em> | ||
18 | {% endif %} | ||
19 | </div> | ||
20 | {% endblock %} |
swtstore/templates/my_sweets.html
1 | {% extends "layout.html" %} | ||
---|---|---|---|
2 | |||
3 | {% block body %} | ||
4 | |||
5 | {% if sweets|length > 0 %} | ||
6 | |||
7 | <ul class="entries unstyled"> | ||
8 | {% for sweet in sweets %} | ||
9 | <li> | ||
10 | <span class="who"> | ||
11 | <a href="#"> | ||
12 | @{{ sweet.who.username }} | ||
13 | </a> | ||
14 | </span> | ||
15 | <span class="what"> | ||
16 | <b> #{{ sweet.what.name }} </b> | ||
17 | </span> | ||
18 | <span class="where"> | ||
19 | {{ sweet.where }} | ||
20 | </span> | ||
21 | <p></p> | ||
22 | <span class="how"> | ||
23 | {{ sweet.how|tojson }} | ||
24 | </span> | ||
25 | |||
26 | <small><i>created: {{sweet.created }}</i></small> | ||
27 | |||
28 | <span class="pull-right permalink"> | ||
29 | <a href="#"> | ||
30 | <i class="glyphicon glyphicon-share"></i> | ||
31 | </a> | ||
32 | </span> | ||
33 | <span class="pull-right edit-sweet" for="{{ sweet.id }}"> | ||
34 | <a href="#"> | ||
35 | <i class="glyphicon glyphicon-edit"></i> | ||
36 | </a> | ||
37 | </span> | ||
38 | </li> | ||
39 | {% endfor %} | ||
40 | </ul> | ||
41 | |||
42 | {% else %} | ||
43 | <div class="row"> | ||
44 | <div class="col-md-5"> | ||
45 | <h4><em>Unbelievable! No sweets yet!!</em></h4> | ||
46 | </div> | ||
47 | </div> | ||
48 | {% endif %} | ||
49 | |||
50 | <div class="modal fade" id="edit-sweet-modal"> | ||
51 | <div class="modal-dialog"> | ||
52 | <div class="modal-content"> | ||
53 | <div class="modal-header"> | ||
54 | <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> | ||
55 | <h4 class="modal-title">Edit Sweet </h4> | ||
56 | </div> | ||
57 | <div class="modal-body"></div> | ||
58 | <div | ||
59 | class="modal-footer"> | ||
60 | <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> | ||
61 | <button type="button" class="btn btn-primary" id="save-edited-sweet">Save changes</button> | ||
62 | </div> | ||
63 | </div><!-- /.modal-content --> | ||
64 | </div><!-- /.modal-dialog --> | ||
65 | </div><!-- /.modal --> | ||
66 | |||
67 | {% endblock %} |
swtstore/templates/specific_sweet.html
1 | {% extends "layout.html" %} | ||
---|---|---|---|
2 | |||
3 | {% block body %} | ||
4 | |||
5 | <ul class="entries unstyled"> | ||
6 | <li> | ||
7 | <span class="who"> | ||
8 | <a href="#"> | ||
9 | @{{ sweet.who.username }} | ||
10 | </a> | ||
11 | </span> | ||
12 | <span class="what"> | ||
13 | <b> #{{ sweet.what.name }} </b> | ||
14 | </span> | ||
15 | <span class="where"> | ||
16 | {{ sweet.where }} | ||
17 | </span> | ||
18 | <p></p> | ||
19 | <span class="how"> | ||
20 | {{ sweet.how|escape|safe }} | ||
21 | </span> | ||
22 | |||
23 | <small><i>created: {{sweet.created }}</i></small> | ||
24 | </li> | ||
25 | </ul> | ||
26 | |||
27 | {% endblock %} |