Skip to content

Commit

Permalink
Merge pull request #688 from Cal-CS-61A-Staff/v3/sumukh/version_api
Browse files Browse the repository at this point in the history
Add Version Model+API
  • Loading branch information
knrafto committed Feb 15, 2016
2 parents 456b2d8 + 74246d9 commit d252136
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 55 deletions.
10 changes: 9 additions & 1 deletion manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from flask.ext.migrate import Migrate, MigrateCommand
from server import create_app
from server.models import db, User, Course, Assignment, Enrollment, \
Backup, Message, Group
Backup, Message, Group, Version

# default to dev config because no one should use this in
# production anyway.
Expand Down Expand Up @@ -57,6 +57,14 @@ def seed():
future = datetime.now() + timedelta(days=1)
db.session.commit()

# Add client version info.
okversion = Version(name="ok", current_version="v1.5.0",
download_link="https://github.com/Cal-CS-61A-Staff/ok-client/releases/download/v1.5.0/ok")
db.session.add(okversion)
okversion = Version(name="ok2", current_version="v1.5.0",
download_link="https://github.com/Cal-CS-61A-Staff/ok-client/releases/download/v1.5.0/ok")
db.session.add(okversion)

students = [User(email='student{}@okpy.org'.format(i)) for i in range(60)]
db.session.add_all(students)

Expand Down
129 changes: 87 additions & 42 deletions server/controllers/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

"""
API.py - /api/{version}/endpoints
Expand All @@ -17,21 +16,18 @@ def get(self, user):
api.add_resource(UserAPI, '/v3/user')
"""

from flask import Blueprint, jsonify, request

import flask_restful as restful
from flask_restful import reqparse, fields, marshal_with

import datetime
import json

from flask_restful.representations.json import output_json

from functools import wraps

from flask import Blueprint, jsonify, request
from flask.ext.login import current_user
import flask_restful as restful
from flask_restful import reqparse, fields, marshal_with
from flask_restful.representations.json import output_json

from server.extensions import cache
from server.utils import encode_id
import server.models as models


Expand Down Expand Up @@ -77,10 +73,14 @@ def envelope_api(data, code, headers=None):
code is the HTTP status code as an int
message will always be sucess since the request did not fail.
"""
message = 'success'
if 'message' in data:
message = data['message']
del data['message']
data = {
'data': data,
'code': code,
'message': 'success'
'message': message
}
return output_json(data, code, headers)

Expand All @@ -103,10 +103,9 @@ def wrapper(*args, **kwargs):
def check_version(func):
@wraps(func)
def wrapper(*args, **kwargs):
supplied = request.args.get('client_version', '')
current_version, download_link = 'v1.5.0', 'https://github.com/Cal-CS-61A-Staff/ok-client/releases/download/v1.5.0/ok'
# TODO Actual Version Check
# Ok Client will still get updates from the ok-server
supplied = request.args.get('client_version')
client = request.args.get('client_name', 'ok') # ok-client doesn't send this right now
current_version, download_link = get_current_version(client)
if not supplied or supplied == current_version:
return func(*args, **kwargs)

Expand All @@ -133,13 +132,22 @@ def name_to_assign_id(name):
if assgn:
return assgn.id

@cache.memoize(1000)
def get_current_version(name):
version = models.Version.query.filter_by(name=name).one_or_none()
if version:
return version.current_version, version.download_link
return None, None

class Resource(restful.Resource):
version = 'v3'
method_decorators = [authenticate, check_version]
# applies to all inherited resources

def __repr__(self):
return "<Resource {}>".format(self.__class__.__name__)

def make_response(self, data, *args, **kwargs):
data = {'data': data}
return super().make_response(data, *args, **kwargs)

def can(object, user, course, action):
Expand All @@ -151,14 +159,12 @@ def can(object, user, course, action):
class PublicResource(Resource):
method_decorators = []


class v3Info(PublicResource):
def get(self):
return {
'version': API_VERSION,
'url': '/api/{}/'.format(API_VERSION),
'documentation': 'http://github.com/Cal-CS-61A-Staff/ok/wiki',
'test': name_to_assign_id('ds8/test12/test')
'documentation': 'http://github.com/Cal-CS-61A-Staff/ok/wiki'
}

# Fewer methods/APIs as V1 since the frontend will not use the API
Expand Down Expand Up @@ -222,13 +228,21 @@ class MessageSchema(APISchema):
'created': fields.DateTime(dt_format='rfc822')
}

class CourseSchema(APISchema):
get_fields = {
'id': fields.Integer,
'offering': fields.String,
'display_name': fields.String,
'active': fields.Boolean,
}


class BackupSchema(APISchema):

get_fields = {
'id': fields.Integer,
'submitter': fields.Integer,
'assignment': fields.Integer,
'submitter': fields.String,
'assignment': fields.String,
'messages': fields.List(fields.Nested(MessageSchema.get_fields)),
'client_time': fields.DateTime(dt_format='rfc822'),
'created': fields.DateTime(dt_format='rfc822')
Expand All @@ -237,8 +251,8 @@ class BackupSchema(APISchema):
post_fields = {
'email': fields.String,
'key': fields.String,
'course': fields.String,
'assign': fields.String,
'course': fields.Nested(CourseSchema.get_fields),
'assignment': fields.String,
}

def __init__(self):
Expand All @@ -259,14 +273,6 @@ def store_backup(self, user):
return backup


class CourseSchema(APISchema):
get_fields = {
'id': fields.Integer,
'name': fields.String,
'display_name': fields.String,
'active': fields.Boolean,
}


class ParticipationSchema(APISchema):
get_fields = {
Expand All @@ -281,6 +287,18 @@ class EnrollmentSchema(APISchema):
'courses': fields.List(fields.Nested(ParticipationSchema.get_fields))
}

class VersionSchema(APISchema):

version_fields = {
'name': fields.String(),
'current_version': fields.String(),
'download_link': fields.String(),
}

get_fields = {
'results': fields.List(fields.Nested(version_fields))
}


# TODO: should be two classes, one for /backups/ and one for /backups/<int:key>/
class Backup(Resource):
Expand All @@ -301,35 +319,62 @@ def get(self, user, key=None):
restful.abort(403)
return backup

@marshal_with(schema.post_fields)
def post(self, user, key=None):
if key is not None:
restful.abort(405)
backup = self.schema.store_backup(user)
return {
'email': current_user.email,
'key': backup.id,
'course': backup.assignment.course_id,
'assignment': backup.assignment_id
'key': encode_id(backup.id),
'course': backup.assignment.course,
'assignment': backup.assignment.name
}


class Enrollment(PublicResource):
""" View what courses students are enrolled in.
Authenticated. Permissions: >= User
class Enrollment(Resource):
""" View what courses an email is enrolled in.
Authenticated. Permissions: >= User or admins.
Used by: Ok Client Auth
"""
model = models.Enrollment
schema = EnrollmentSchema()

@marshal_with(schema.get_fields)
def get(self, email):
course = request.args.get('course', '') # TODO use reqparse
user = models.User.lookup(email)
if course and user:
def get(self, user, email):
target = models.User.lookup(email)
if not self.can('view', target, user):
restful.abort(403)
if target:
return {'courses': user.participations}
return {'courses': []}

api.add_resource(v3Info, '/v3')
@staticmethod
def can(action, resource, requester):
if requester.is_admin:
return True
return resource == requester

class Version(PublicResource):
""" Current version of a client
Permissions: World Readable
Used by: Ok Client Auth
"""
model = models.Version
schema = VersionSchema()

@marshal_with(schema.get_fields)
@cache.memoize(600)
def get(self, name=None):
if name:
versions = self.model.query.filter_by(name=name).all()
else:
versions = self.model.query.all()
return {'results': versions}


api.add_resource(v3Info, '/v3/')

api.add_resource(Backup, '/v3/backups/', '/v3/backups/<int:key>/')
api.add_resource(Enrollment, '/v3/enrollment/<string:email>')
api.add_resource(Enrollment, '/v3/enrollment/<string:email>/')
api.add_resource(Version, '/v3/version/', '/v3/version/<string:name>')
12 changes: 11 additions & 1 deletion server/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,10 @@ def files(self):
backup_id=self.id,
kind='file_contents').first()
if message:
return message.contents
contents = dict(message.contents)
# submit is not a real file, but the client sends it anyway
contents.pop('submit', None)
return contents
else:
return {}

Expand Down Expand Up @@ -590,3 +593,10 @@ class GroupAction(db.Model, TimestampMixin):
# see Group.serialize for format
group_before = db.Column(JSON)
group_after = db.Column(JSON)

class Version(db.Model, TimestampMixin, DictMixin):
id = db.Column(db.Integer(), primary_key=True)
# software name e.g. 'ok'
name = db.Column(db.String(255), nullable=False, unique=True, index=True)
current_version = db.Column(db.String(255), nullable=False)
download_link = db.Column(db.Text())
14 changes: 12 additions & 2 deletions server/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@
<div class="stripe"></div>
<div class="stripe"></div>
</div>
<a href="{{ url_for('auth.login') }}" class="btn pill">Login</a>
{% if config['TESTING_LOGIN'] and config['ENV'] == 'dev' %}
<a href="{{ url_for('auth.testing_login') }}" class="btn pill">Login</a>
{% else %}
<a href="{{ url_for('auth.login') }}" class="btn pill">Login</a>
{% endif %}

</div>
</header>
Expand All @@ -42,7 +46,13 @@
{% include 'alerts.html' %}
<h1 class="title">Automate grading. Personalize feedback.</h1>
<p>Ok autogrades coding assignments and facilitates code submission, review, composition, and analytics for advanced diagnoses for your class.</p>
<a href="{{ url_for('auth.login') }}" class="button">Login with Google</a>

{% if config['TESTING_LOGIN'] and config['ENV'] == 'dev' %}
<a href="{{ url_for('auth.testing_login') }}" class="button">Testing Login</a>
{% else %}
<a href="{{ url_for('auth.login') }}" class="button">Login with Google</a>
{% endif %}

</div>
</div>
</body>
Expand Down
17 changes: 15 additions & 2 deletions server/templates/testing-login.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,21 @@
Login for Testing
</div>
<div class="login-box-body">
<p class="login-box-msg">Impersonate a user</p>
{% for user in ['okstaff@okpy.org', 'student0@okpy.org'] %}
<form action="{{ callback }}" method="post">
<div class="form-group has-feedback">
<input type="hidden" value="{{user}}" class="form-control" name="email" placeholder="Email">
</div>
<div class="row">
<button type="submit" class="btn btn-primary btn-block btn-flat">Sign In As {{user}}</button>
</div>
</form>
{% endfor %}
</div>

<div class="login-box-body">

<p class="login-box-msg">Impersonate a user</p>

<form action="{{ callback }}" method="post">
<div class="form-group has-feedback">
Expand All @@ -17,7 +31,6 @@
<div class="row">
<button type="submit" class="btn btn-primary btn-block btn-flat">Sign In</button>
</div>
</div>
</form>
</div>
</div>
Expand Down

0 comments on commit d252136

Please sign in to comment.