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

Points store app #2313

Merged
merged 10 commits into from
Aug 19, 2014
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* Config saved to config.json and https://gist.github.com/90ea88eec00c6a7c0e94
*/
/*! normalize.css v3.0.1 | MIT License | git.io/normalize */
html {
/*html {
font-family: sans-serif;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
Expand Down Expand Up @@ -263,7 +263,7 @@ th {
font-family: 'Glyphicons Halflings';
src: url('../../fonts/control_panel/glyphicons-halflings-regular.eot');
src: url('../../fonts/control_panel/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../../fonts/control_panel/glyphicons-halflings-regular.woff') format('woff'), url('../../fonts/control_panel/glyphicons-halflings-regular.ttf') format('truetype'), url('../../fonts/control_panel/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
}
}*/
.glyphicon {
position: relative;
top: 1px;
Expand Down Expand Up @@ -875,7 +875,7 @@ th {
.glyphicon-tree-deciduous:before {
content: "\e200";
}
* {
/** {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
Expand All @@ -885,8 +885,8 @@ th {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html {
}*/
/*html {
font-size: 10px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
Expand Down Expand Up @@ -924,7 +924,7 @@ figure {
}
img {
vertical-align: middle;
}
}*/
.img-responsive {
display: block;
width: 100% \9;
Expand Down
18 changes: 14 additions & 4 deletions kalite/distributed/static/js/distributed/distributed-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ var StatusModel = Backbone.Model.extend({

this.loaded.then(this.after_loading);

this.listenTo(this, "change:points", this.update_total_points);
this.listenTo(this, "change:newpoints", this.update_total_points);

},

get_server_time: function () {
Expand All @@ -96,7 +99,16 @@ var StatusModel = Backbone.Model.extend({
toggle_state("student", !self.get("is_admin") && !self.get("is_django_user") && self.get("is_logged_in"));
toggle_state("admin", self.get("is_admin")); // combination of teachers & super-users
});

this.update_total_points();

},

update_total_points: function() {
// add the points that existed at page load and the points earned since page load, to get the total current points
this.set("totalpoints", this.get("points") + this.get("newpoints"));
}

});

// create a global StatusModel instance to hold shared state, mostly as returned by the "status" api call
Expand All @@ -110,16 +122,14 @@ var TotalPointView = Backbone.View.extend({

initialize: function() {
_.bindAll(this);
this.model.bind("change:points", this.render);
this.model.bind("change:newpoints", this.render);
this.model.bind("change:totalpoints", this.render);
this.model.bind("change:username", this.render);
this.render();
},

render: function() {

// add the points that existed at page load and the points earned since page load, to get the total current points
var points = this.model.get("points") + this.model.get("newpoints");
var points = this.model.get("totalpoints");
var username_span = sprintf("<span id='logged-in-name'>%s</span>", this.model.get("username"));
var message = null;

Expand Down
1 change: 1 addition & 0 deletions kalite/distributed/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
url(r'^exercisedashboard/$', 'exercise_dashboard', {}, 'exercise_dashboard'),
url(r'^search/$', 'search', {}, 'search'),
url(r'^test/', include('student_testing.urls')),
url(r'^store/', include('store.urls')),
# the following pattern is a catch-all, so keep it last:
# Allows remote admin of the distributed server
url(r'^cryptologin/$', 'crypto_login', {}, 'crypto_login'),
Expand Down
13 changes: 13 additions & 0 deletions kalite/store/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.contrib import admin

from .models import *


class StoreItemAdmin(admin.ModelAdmin):
list_display = ("title", "cost",)
admin.site.register(StoreItem, StoreItemAdmin)


class StoreTransactionLogAdmin(admin.ModelAdmin):
list_display = ("id", "item", "user", "purchased_at")
admin.site.register(StoreTransactionLog, StoreTransactionLogAdmin)
2 changes: 2 additions & 0 deletions kalite/store/api_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ class Meta:
"resource_type": ('exact', ),
}


class StoreTransactionLogResource(ModelResource):

user = fields.ForeignKey(FacilityUserResource, 'user')
item = fields.ForeignKey(StoreItemResource, 'item')

class Meta:
queryset = StoreTransactionLog.objects.all()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="store-item">
<img class="store-item-image" src="{{ thumbnail }}"/>
<div class="store-item-metadata">
<div class="store-item-title">{{ title }}</div>
<div class="store-item-description">{{ description }}</div>
<button class="store-item-purchase-button btn btn-primary">Purchase ({{ cost }} points)</button>
</div>
<div class="clear"></div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div class="store-purchased-item">
<img class="store-item-image" src="{{ thumbnail }}"/>
<div class="store-item-title">{{ title }}</div>
<div class="clear"></div>
</div>
7 changes: 7 additions & 0 deletions kalite/store/hbtemplates/store/store-wrapper.handlebars
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<h2>Items Available for Purchase (<span class="points-remaining">{{ points }}</span> points remaining)</h2>
<div class="available-store-items"></div>
<div class="clear"></div>
<br/><br/>
<h2>Your Purchased Items</h2>
<div class="purchased-store-items"></div>
<div class="clear"></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models


class Migration(SchemaMigration):

def forwards(self, orm):
# Adding field 'StoreTransactionLog.purchased_at'
db.add_column(u'store_storetransactionlog', 'purchased_at',
self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True),
keep_default=False)


def backwards(self, orm):
# Deleting field 'StoreTransactionLog.purchased_at'
db.delete_column(u'store_storetransactionlog', 'purchased_at')


models = {
'securesync.device': {
'Meta': {'object_name': 'Device'},
'counter': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'public_key': ('django.db.models.fields.CharField', [], {'max_length': '500', 'db_index': 'True'}),
'signature': ('django.db.models.fields.CharField', [], {'max_length': '360', 'null': 'True', 'blank': 'True'}),
'signed_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Device']"}),
'signed_version': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'version': ('django.db.models.fields.CharField', [], {'default': "'0.9.2'", 'max_length': '9', 'blank': 'True'}),
'zone_fallback': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Zone']"})
},
'securesync.facility': {
'Meta': {'object_name': 'Facility'},
'address': ('django.db.models.fields.CharField', [], {'max_length': '400', 'blank': 'True'}),
'address_normalized': ('django.db.models.fields.CharField', [], {'max_length': '400', 'blank': 'True'}),
'contact_email': ('django.db.models.fields.EmailField', [], {'max_length': '60', 'blank': 'True'}),
'contact_name': ('django.db.models.fields.CharField', [], {'max_length': '60', 'blank': 'True'}),
'contact_phone': ('django.db.models.fields.CharField', [], {'max_length': '60', 'blank': 'True'}),
'counter': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'latitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
'longitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'signature': ('django.db.models.fields.CharField', [], {'max_length': '360', 'null': 'True', 'blank': 'True'}),
'signed_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Device']"}),
'signed_version': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'user_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'zone_fallback': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Zone']"}),
'zoom': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'})
},
'securesync.facilitygroup': {
'Meta': {'object_name': 'FacilityGroup'},
'counter': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'facility': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['securesync.Facility']"}),
'id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'signature': ('django.db.models.fields.CharField', [], {'max_length': '360', 'null': 'True', 'blank': 'True'}),
'signed_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Device']"}),
'signed_version': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'zone_fallback': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Zone']"})
},
'securesync.facilityuser': {
'Meta': {'object_name': 'FacilityUser'},
'counter': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'default_language': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'facility': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['securesync.Facility']"}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['securesync.FacilityGroup']", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'is_teacher': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '60', 'blank': 'True'}),
'notes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'signature': ('django.db.models.fields.CharField', [], {'max_length': '360', 'null': 'True', 'blank': 'True'}),
'signed_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Device']"}),
'signed_version': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'username': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'zone_fallback': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Zone']"})
},
'securesync.zone': {
'Meta': {'object_name': 'Zone'},
'counter': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'signature': ('django.db.models.fields.CharField', [], {'max_length': '360', 'null': 'True', 'blank': 'True'}),
'signed_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Device']"}),
'signed_version': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'zone_fallback': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Zone']"})
},
u'store.storeitem': {
'Meta': {'object_name': 'StoreItem'},
'cost': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'counter': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'resource_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'resource_type': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'returnable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'signature': ('django.db.models.fields.CharField', [], {'max_length': '360', 'null': 'True', 'blank': 'True'}),
'signed_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Device']"}),
'signed_version': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'thumbnail': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'zone_fallback': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Zone']"})
},
u'store.storetransactionlog': {
'Meta': {'object_name': 'StoreTransactionLog'},
'context_id': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'context_type': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'counter': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'item': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['store.StoreItem']"}),
'purchased_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'reversible': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'signature': ('django.db.models.fields.CharField', [], {'max_length': '360', 'null': 'True', 'blank': 'True'}),
'signed_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Device']"}),
'signed_version': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['securesync.FacilityUser']"}),
'value': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'zone_fallback': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['securesync.Zone']"})
}
}

complete_apps = ['store']
1 change: 1 addition & 0 deletions kalite/store/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class StoreTransactionLog(DeferredCountSyncedModel):
# can this transaction be undone by user actions? Initially set by 'returnable' on StoreItem, but can be changed subsequently
reversible = models.BooleanField(default=False)
item = models.ForeignKey(StoreItem, db_index=True)
purchased_at = models.DateTimeField(blank=True, null=True)

class Meta: # needed to clear out the app_name property from SyncedClass.Meta
pass
Expand Down
34 changes: 34 additions & 0 deletions kalite/store/static/css/store/store.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.store-item, .store-purchased-item {
background-color: #EEE;
border-radius: 10px;
padding: 10px;
margin-bottom: 10px;
}

.store-item .store-item-image {
width: 20%;
max-width: 200px;
float: left;
}

.store-purchased-item {
float: left;
margin-right: 10px;
}

.store-purchased-item .store-item-image {
height: 10%;
max-height: 100px;
float: left;
}

.store-item-metadata {
width: 75%;
float: left;
padding-left: 3%;
}

.store-item-title {
font-weight: bold;
font-size: 1.2em;
}
27 changes: 27 additions & 0 deletions kalite/store/static/js/store/models.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
window.AvailableStoreItemModel = Backbone.Model.extend({

});

window.PurchasedStoreItemModel = Backbone.Model.extend({
urlRoot: "/api/store/storetransactionlog/"
});

window.AvailableStoreItemCollection = Backbone.Collection.extend({

url: "/api/store/storeitem/",

model: AvailableStoreItemModel
});


window.PurchasedStoreItemCollection = Backbone.Collection.extend({

model: PurchasedStoreItemModel,

url: function() {
return "/api/store/storetransactionlog/?" + $.param({
user: statusModel.get("user_id")
});
}

});
Loading