Merge branch 'develop' of sika:vtmk/web2.0 into develop

This commit is contained in:
HooVee
2016-10-04 18:02:22 +03:00
37 changed files with 15148 additions and 56 deletions
+4 -3
View File
@@ -1,9 +1,10 @@
from django.contrib import admin
from infoscreen.models import Rotation, InfoItem, InfoInstance, InfoOption
from infoscreen.models import Rotation, InfoItem, InfoInstance, ImageInfoItem, ExternalImageInfoItem, ABBInfoItem
# Register your models here.
admin.site.register(Rotation)
admin.site.register(InfoItem)
admin.site.register(ImageInfoItem)
admin.site.register(ExternalImageInfoItem)
admin.site.register(ABBInfoItem)
admin.site.register(InfoInstance)
admin.site.register(InfoOption)
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-09-21 12:52
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0004_auto_20160914_1747'),
]
operations = [
migrations.RemoveField(
model_name='infoinstance',
name='item',
),
migrations.RemoveField(
model_name='infoinstance',
name='rotation',
),
migrations.RemoveField(
model_name='infooption',
name='item',
),
migrations.DeleteModel(
name='InfoInstance',
),
migrations.DeleteModel(
name='InfoItem',
),
migrations.DeleteModel(
name='InfoOption',
),
]
@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-09-21 12:54
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('infoscreen', '0005_auto_20160921_1252'),
]
operations = [
migrations.CreateModel(
name='InfoInstance',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('duration', models.FloatField(default=15.0)),
('item_id', models.PositiveIntegerField()),
('item_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
('rotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='infoscreen.Rotation')),
],
),
migrations.CreateModel(
name='InfoItem',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('expire_date', models.DateTimeField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='ABBInfoItem',
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
],
bases=('infoscreen.infoitem',),
),
migrations.CreateModel(
name='ExternalImageInfoItem',
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
('url', models.TextField()),
],
bases=('infoscreen.infoitem',),
),
migrations.CreateModel(
name='ImageInfoItem',
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
('img', models.ImageField(upload_to='infoimages/')),
],
bases=('infoscreen.infoitem',),
),
]
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-09-27 18:06
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('infoscreen', '0006_abbinfoitem_externalimageinfoitem_imageinfoitem_infoinstance_infoitem'),
]
operations = [
migrations.CreateModel(
name='SossoInfoItem',
fields=[
('infoitem_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='infoscreen.InfoItem')),
],
bases=('infoscreen.infoitem',),
),
]
+174 -32
View File
@@ -1,60 +1,194 @@
from django.db import models
from django import forms
from django.utils import timezone
from datetime import datetime
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class InfoItem(models.Model):
class __meta__:
abstract = True
name = models.CharField(max_length=255)
template_url = models.CharField(max_length=512)
expire_date = models.DateTimeField(blank=True,null=True) # None means never expiring item
def get_dict(self):
# fetch options
opts = {}
for o in self.options.all():
opts[o.key] = o.value
# parse dict
return {
'name': self.name,
'template_url': self.template_url,
'options': opts
def get_template_url(self):
raise NotImplementedError("inheriting classes must implement get_template_url")
@staticmethod
def get_create_template_url():
raise NotImplementedError("inheriting classes must implement get_create_template_url")
@classmethod
def create_from_dict(cls,d):
item = cls()
item.update_from_dict(d)
return item
def update_from_dict(self,d):
try:
expire_date = d.pop('expire_date', None)
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
except:
pass
dmap = {
'name': 'name',
}
def __str__(self):
return self.name
class InfoInstance(models.Model):
rotation = models.ForeignKey('Rotation', related_name='instances')
item = models.ForeignKey('InfoItem')
duration = models.FloatField(default=15.0) # seconds
for k,v in d.items():
try:
self.__setattr__(dmap[k],v)
except KeyError:
pass
self.save()
def get_dict(self):
return {
'id': self.id,
'name': self.name,
'item_type': ContentType.objects.get_for_model(self).id,
'template_url': self.get_template_url(),
'create_template_url': self.get_create_template_url(),
'options': {}
}
def delete(self):
# since generic foreignkeys suck. delete infoitems pointing here manually
InfoInstance.objects.filter(item_id=self.id, item_type=ContentType.objects.get_for_model(self)).delete()
super().delete()
@classmethod
def get_subclasses(cls):
for subclass in cls.__subclasses__():
yield from subclass.get_subclasses()
yield subclass
def __str__(self):
return self.name
class ABBInfoItem(InfoItem):
def get_template_url(self):
return "/static/html/abb.html"
@staticmethod
def get_create_template_url():
return "/static/html/abb_create.html"
class SossoInfoItem(InfoItem):
def get_template_url(self):
return "/static/html/sosso.html"
@staticmethod
def get_create_template_url():
return "/static/html/sosso_create.html"
class ImageInfoItem(InfoItem):
img = models.ImageField(upload_to="infoimages/")
def get_template_url(self):
#get param to avoid angular from optimizing same template with different options
return "/static/html/generic_image.html?img={}".format(self.name)
@staticmethod
def get_create_template_url():
return "/static/html/generic_image_create.html"
def get_dict(self):
d = super().get_dict()
d["options"] = {'img': self.img.url}
return d
class ExternalImageInfoItem(InfoItem):
url = models.TextField()
def get_template_url(self):
return "/static/html/generic_image.html?img={}".format(self.name)
@staticmethod
def get_create_template_url():
return "/static/html/generic_external_image_create.html"
def get_dict(self):
d = super().get_dict()
d["options"] = {'img': self.url}
return d
@classmethod
def create_from_dict(cls,d):
item = cls()
item.update_from_dict(d)
return item
def update_from_dict(self,d):
try:
expire_date = d.pop('expire_date', None)
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
except:
pass
dmap = {
'name': 'name',
'url': 'url',
}
for k,v in d.items():
try:
self.__setattr__(dmap[k],v)
except KeyError:
pass
self.save()
class InfoInstance(models.Model):
rotation = models.ForeignKey('Rotation', related_name='instances')
duration = models.FloatField(default=15.0) # seconds
# generic relation to somekind of InfoItem
item_id = models.PositiveIntegerField()
item_type = models.ForeignKey(ContentType,on_delete=models.CASCADE)
item = GenericForeignKey('item_type','item_id')
@classmethod
def create_from_dict(cls,d):
try:
rotation = Rotation.objects.get(pk=int(d["rotation_id"]))
ct = ContentType.objects.get_for_id(int(d["item_type"]))
item = ct.get_object_for_this_type(pk=int(d["item_id"]))
duration = float(d["duration"])
except:
raise RuntimeError("invalid parameters supplied supplied")
try:
return cls.objects.create(
rotation=rotation,
item=item,
duration=duration
)
except:
raise RuntimeError("error while adding instance to db")
def get_dict(self):
return {
'id':self.id,
'item': self.item.get_dict(),
'duration': self.duration,
}
def __str__(self):
return "{}: {} ({}s)".format(self.rotation.name, self.item.name, self.duration)
class InfoOption(models.Model):
class __meta__:
unique_together = ("item", "key")
item = models.ForeignKey('InfoItem', related_name='options')
key = models.CharField(max_length=255)
value = models.CharField(max_length=255)
def __str__(self):
return "{}: {} -> {}".format(self.item.name, self.key, self.value)
class Rotation(models.Model):
name = models.CharField(max_length=255)
def get_dict(self):
# exclude expired items from rotation (note: using filter would exclude items with no expide_date)
instances = self.instances.exclude(item__expire_date__lt=timezone.now())
instance_list = list(map(lambda i:i.get_dict(), instances))
# exclude expired items from rotation (note: using tricky syntax to avoid excluding items with no expire_date)
now = timezone.now()
instances = self.instances.all()
filtered = filter(lambda i: (i.item.expire_date or now) >= now, list(instances))
instance_list = list(map(lambda i:i.get_dict(), filtered))
return {
'id':self.id,
'name': self.name,
'instances': instance_list,
}
def get_list(self):
return {
'id':self.id,
'name':self.name,
}
def __str__(self):
return self.name
@@ -73,3 +207,11 @@ class ABBJob(models.Model):
def __str__(self):
return self.title
class ImageUploadForm(forms.Form):
'''
Form used to handle imageuploads to
infoscreen app
'''
name = forms.CharField()
image = forms.ImageField()
+7
View File
@@ -0,0 +1,7 @@
<div ng-controller="infoadmin_abbitem_create" style="margin-top:20px;">
<div class="form-group">
<label>Name:</label>
<input type="text" ng-model="item.name"></input>
</div>
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
</div>
@@ -0,0 +1,11 @@
<div ng-controller="infoadmin_externalimage_create" style="margin-top:20px;">
<div class="form-group">
<label>Name:</label>
<input type="text" ng-model="item.name"></input>
</div>
<div class="form-group">
<label>Url:</label>
<input type="text" ng-model="item.url"></input>
</div>
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
</div>
@@ -0,0 +1,10 @@
<div ng-controller="infoadmin_image_create" style="margin-top:20px;">
<div class="form-group">
<label>Name:</label>
<input type="text" ng-model="imagename"></input>
</div>
<div class="form-group">
<input type="file" ngf-select ng-model="img" name="file" required>
</div>
<input type="button" class="btn btn-success" ng-click="send(img);" value="create"></input>
</div>
@@ -0,0 +1,39 @@
<div class="row" ng-controller="infoadmin_ctrl">
<div class="col-xs-12 col-md-6">
<h2>Info items</h2>
<table class="table table-striped">
<tr><th>Item</th><th>Set duration</th><th>Add to rotation</th><th>Delete</th></tr>
<tr ng-repeat="i in infoitems">
<td>{{i.name}}</td>
<td><input type="text" ng-model="i.duration"></input></td>
<td><input type="button" class="btn btn-success" ng-click="createInstance(selected_rot.id, i.id, i.item_type, i.duration);" value="add"></input></td>
<td><input type="button" class="btn btn-danger" ng-click="deleteItem(i.item_type, i.id);" value="delete"></input></td>
</tr>
</table>
<h2>Create new item</h2>
Item type
<select ng-model="createtype", ng-options="t.name for t in infotypes">
<option value=""></option>
</select>
<div ng-include="createtype.create_template_url"></div>
</div>
<div class="col-xs-12 col-md-6">
<h2>Rotations</h2>
<table class="table table-striped">
<tr><th>Rotation</th><th>Select</th></tr>
<tr ng-repeat="r in rotations">
<td>{{r.name}}</td>
<td><input type="button" class="btn btn-info" ng-click="selectRotation(r.id)" value="select"></td>
</tr>
</table>
<h2>Rotation: {{selected_rot.name}}</h2>
<table class="table table-striped">
<tr><th>Instance</th><th>Duration</th><th>Delete</th></tr>
<tr ng-repeat="i in selected_rot.instances">
<td>{{i.item.name}}</td><td>{{i.duration}}s</td>
<td><input type="button" ng-click="deleteInstance(i.id);" value="delete" class="btn btn-danger"></input><td>
</tr>
</table
</div>
</div
+19
View File
@@ -0,0 +1,19 @@
<div ng-controller="SossoController">
<div class="row" style="max-height:300px">
<div class="col-xs-4 col-xs-offset-1" style="padding-top:30px;padding-bottom:30px"><img src=""></div>
<div class="col-xs-7" style="font-size:100px;padding-top:30px">JUTUT</div>
</div>
<div class="row" ng-repeat="post in data.posts" style="height:150px;border:2px solid black;color:#33333">
<div class="row" style="max-height:100px">
<div class="col-xs-1 col-xs-offset-2"><img ng-src="{{ post.thumbnail }}" style="height:150px;"></div>
<div class="col-xs-8 col-xs-offset-1">
<h1 style="font-size:48px">{{ post.title }}</h1>
<div class="row">
<div class="col-xs-offset-6" style="font-size:12px">{{ post.author.first_name }} {{ post.author.last_name }}</div>
<div class="col-xs-offset-6" style="font-size:12px">{{ post.date }}</div>
</div>
</div>
</div>
</div>
</div>
+7
View File
@@ -0,0 +1,7 @@
<div ng-controller="infoadmin_sossoitem_create" style="margin-top:20px;">
<div class="form-group">
<label>Name:</label>
<input type="text" ng-model="item.name"></input>
</div>
<input type="button" class="btn btn-success" ng-click="send()" value="create"></input>
</div>
@@ -0,0 +1,136 @@
var app = angular.module('infoAdmin',['ngFileUpload']);
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
}]);
app.service("InstanceList", ["$http", function($http){
var self = this;
this.selected_rot = {}
this.rotations = [];
this.listInstances = function() {
return self.selected_rot;
};
this.listRotations = function(){
return self.rotations;
}
this.createInstance = function(rotation_id, item_id, item_type, duration){
var data = {
'rotation_id': rotation_id,
'item_id': item_id,
'item_type': item_type,
'duration': duration,
}
$http.post("/infoscreen/instance", data).then(function(response){
self.getRotation(rotation_id);
});
};
this.getRotation = function(id){
$http.get("/infoscreen/rotation/"+id).then(function(response){
self.selected_rot = response.data;
});
};
this.getRotations = function(){
$http.get("/infoscreen/rotations").then(function(response){
self.rotations = response.data;
// select first rotation by default if we have any
if (self.rotations.length > 0){
self.getRotation(self.rotations[0].id);
}
});
}
this.deleteInstance = function(id){
$http.delete("/infoscreen/instance/"+id).then(function(response){
self.getRotation(self.selected_rot.id);
});
}
}]);
app.service("ItemList", ["$http",'InstanceList', function($http, InstanceList){
var self = this;
self.items = []
this.loadItems = function(){
$http.get("/infoscreen/items").then(function(response){
self.items = response.data;
});
};
this.getItems = function(){
return self.items;
};
this.deleteItem = function(type_id, item_id){
$http.delete("/infoscreen/delete_item/"+type_id+"/"+item_id).then(function(){
self.loadItems();
// refresh instances because deleting item may cascade to instances
InstanceList.getRotation(InstanceList.selected_rot.id);
});
};
this.refreshCB = function(response){
self.loadItems();
};
}]);
app.controller('infoadmin_ctrl', function($scope, $http, ItemList, InstanceList){
// init items
$scope.rotations = [];
$scope.infoitems = [];
$scope.selected_rot= {};
// helpers
$scope.deleteItem = ItemList.deleteItem;
$scope.selectRotation = InstanceList.getRotation;
$scope.createInstance = InstanceList.createInstance;
$scope.deleteInstance = InstanceList.deleteInstance;
// fetch data
$scope.$watch(InstanceList.listInstances, function(instances){
$scope.selected_rot = instances;
});
$scope.$watch(InstanceList.listRotations, function(rotations){
$scope.rotations = rotations;
});
InstanceList.getRotations();
$http.get("/infoscreen/types").then(function(response){
$scope.infotypes = response.data;
});
$scope.$watch(ItemList.getItems, function(items){
$scope.infoitems = items
});
ItemList.loadItems();
});
app.controller('infoadmin_externalimage_create', function($scope, $http,ItemList){
$scope.item = {}
$scope.send = function(){
$http.post("/infoscreen/create_external_image", $scope.item).then(ItemList.loadItems)
}
});
app.controller('infoadmin_abbitem_create', function($scope, $http,ItemList){
$scope.item = {}
$scope.send = function(){
$http.post("/infoscreen/create_abbitem", $scope.item).then(ItemList.loadItems)
}
});
app.controller('infoadmin_sossoitem_create', function($scope, $http,ItemList){
$scope.item = {}
$scope.send = function(){
$http.post("/infoscreen/create_sossoitem", $scope.item).then(ItemList.loadItems)
}
});
app.controller('infoadmin_image_create', ['$scope', 'Upload', '$timeout',"ItemList", function ($scope, Upload, $timeout,ItemList) {
$scope.send = function(file) {
console.log("name: "+$scope.imagename);
file.upload = Upload.upload({
url: '/infoscreen/create_image',
data: {name: $scope.imagename, image: file},
}).then(function (response) {
$timeout(function () {
file.result = response.data;
ItemList.loadItems();
$scope.name= ""
$scope.img = {}
});
},function (response) {
if (response.status > 0)
$scope.errorMsg = response.status + ': ' + response.data;
}, function (evt) {
file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total));
});
}
}]);
+14 -6
View File
@@ -1,5 +1,5 @@
var app = angular.module('infoApp', ['ngAnimate', 'ngRoute']);
var app = angular.module('infoApp', ['ngAnimate', 'ngRoute']);
app.controller('infoscreen_main', function($scope,$http,$timeout){
var templates = [];
$scope.init = function(rot){
@@ -8,9 +8,11 @@ app.controller('infoscreen_main', function($scope,$http,$timeout){
}
var get_rotation = function(){
$scope.index = -1;
$http.get('/infoscreen/rotation/'+$scope.rotation).then(function(response){
$http.get('/infoscreen/rotation/'+$scope.rotation, {timeout:5000}).then(function(response){
templates = response.data.instances;
$scope.next();
},function(response){
$timeout(get_rotation, 10000);
});
}
@@ -26,7 +28,7 @@ app.controller('infoscreen_main', function($scope,$http,$timeout){
for (key in temp.item.options){
$scope[key] = temp.item.options[key]
}
}
}
};
$timeout($scope.next, temp.duration * 1000);
}
@@ -37,7 +39,14 @@ app.controller('ABBController', function($scope, $http){
$scope.jobs = response.data;
})
});
app.controller('timetableCtrl',
app.controller('SossoController', function($scope, $http){
$scope.data = [];
$http.get("http://sosso.fi/api/get_recent_posts/?count=" + 3 ).then(function(response)
{
$scope.data = response.data;
})
});
app.controller('timetableCtrl',
function($scope, $http, $interval) {
function load(){
$http.get('/static/js/hsl.json')
@@ -88,7 +97,7 @@ app.controller('timetableCtrl',
if(e){
$scope.arr.push(z);
}
}
}
function pad(num, size) {
@@ -121,4 +130,3 @@ app.controller('timetableCtrl',
var z=$interval(load,60000);
}
);
@@ -0,0 +1,4 @@
angular:angular@1.2.24
danialf:ng-file-upload@12.2.10
meteor@1.1.10
underscore@1.0.4
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 danialfarid
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,5 @@
# angular-file-upload-bower
bower distribution of [angular-file-upload](https://github.com/danialfarid/angular-file-upload).
All issues and pull request must be sumbitted to [angular-file-upload](https://github.com/danialfarid/angular-file-upload)
@@ -0,0 +1,14 @@
{
"name": "ng-file-upload",
"main": "ng-file-upload.js",
"homepage": "https://github.com/danialfarid/ng-file-upload",
"dependencies": {
"angular": ">1.2.0"
},
"authors": [
"danialf <danial.farid@gmail.com>"
],
"description": "Lightweight Angular JS directive to upload files. Support drag&drop, paste image, progress and abort",
"ignore": [],
"license": "MIT"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,421 @@
/**!
* AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort,
* progress, resize, thumbnail, preview, validation and CORS
* FileAPI Flash shim for old browsers not supporting FormData
* @author Danial <danial.farid@gmail.com>
* @version 12.2.11
*/
(function () {
/** @namespace FileAPI.noContentTimeout */
function patchXHR(fnName, newFn) {
window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);
}
function redefineProp(xhr, prop, fn) {
try {
Object.defineProperty(xhr, prop, {get: fn});
} catch (e) {/*ignore*/
}
}
if (!window.FileAPI) {
window.FileAPI = {};
}
if (!window.XMLHttpRequest) {
throw 'AJAX is not supported. XMLHttpRequest is not defined.';
}
FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad;
if (FileAPI.shouldLoad) {
var initializeUploadListener = function (xhr) {
if (!xhr.__listeners) {
if (!xhr.upload) xhr.upload = {};
xhr.__listeners = [];
var origAddEventListener = xhr.upload.addEventListener;
xhr.upload.addEventListener = function (t, fn) {
xhr.__listeners[t] = fn;
if (origAddEventListener) origAddEventListener.apply(this, arguments);
};
}
};
patchXHR('open', function (orig) {
return function (m, url, b) {
initializeUploadListener(this);
this.__url = url;
try {
orig.apply(this, [m, url, b]);
} catch (e) {
if (e.message.indexOf('Access is denied') > -1) {
this.__origError = e;
orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]);
}
}
};
});
patchXHR('getResponseHeader', function (orig) {
return function (h) {
return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h]));
};
});
patchXHR('getAllResponseHeaders', function (orig) {
return function () {
return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this));
};
});
patchXHR('abort', function (orig) {
return function () {
return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this));
};
});
patchXHR('setRequestHeader', function (orig) {
return function (header, value) {
if (header === '__setXHR_') {
initializeUploadListener(this);
var val = value(this);
// fix for angular < 1.2.0
if (val instanceof Function) {
val(this);
}
} else {
this.__requestHeaders = this.__requestHeaders || {};
this.__requestHeaders[header] = value;
orig.apply(this, arguments);
}
};
});
patchXHR('send', function (orig) {
return function () {
var xhr = this;
if (arguments[0] && arguments[0].__isFileAPIShim) {
var formData = arguments[0];
var config = {
url: xhr.__url,
jsonp: false, //removes the callback form param
cache: true, //removes the ?fileapiXXX in the url
complete: function (err, fileApiXHR) {
if (err && angular.isString(err) && err.indexOf('#2174') !== -1) {
// this error seems to be fine the file is being uploaded properly.
err = null;
}
xhr.__completed = true;
if (!err && xhr.__listeners.load)
xhr.__listeners.load({
type: 'load',
loaded: xhr.__loaded,
total: xhr.__total,
target: xhr,
lengthComputable: true
});
if (!err && xhr.__listeners.loadend)
xhr.__listeners.loadend({
type: 'loadend',
loaded: xhr.__loaded,
total: xhr.__total,
target: xhr,
lengthComputable: true
});
if (err === 'abort' && xhr.__listeners.abort)
xhr.__listeners.abort({
type: 'abort',
loaded: xhr.__loaded,
total: xhr.__total,
target: xhr,
lengthComputable: true
});
if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () {
return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status;
});
if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () {
return fileApiXHR.statusText;
});
redefineProp(xhr, 'readyState', function () {
return 4;
});
if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () {
return fileApiXHR.response;
});
var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined);
redefineProp(xhr, 'responseText', function () {
return resp;
});
redefineProp(xhr, 'response', function () {
return resp;
});
if (err) redefineProp(xhr, 'err', function () {
return err;
});
xhr.__fileApiXHR = fileApiXHR;
if (xhr.onreadystatechange) xhr.onreadystatechange();
if (xhr.onload) xhr.onload();
},
progress: function (e) {
e.target = xhr;
if (xhr.__listeners.progress) xhr.__listeners.progress(e);
xhr.__total = e.total;
xhr.__loaded = e.loaded;
if (e.total === e.loaded) {
// fix flash issue that doesn't call complete if there is no response text from the server
var _this = this;
setTimeout(function () {
if (!xhr.__completed) {
xhr.getAllResponseHeaders = function () {
};
_this.complete(null, {status: 204, statusText: 'No Content'});
}
}, FileAPI.noContentTimeout || 10000);
}
},
headers: xhr.__requestHeaders
};
config.data = {};
config.files = {};
for (var i = 0; i < formData.data.length; i++) {
var item = formData.data[i];
if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) {
config.files[item.key] = item.val;
} else {
config.data[item.key] = item.val;
}
}
setTimeout(function () {
if (!FileAPI.hasFlash) {
throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
}
xhr.__fileApiXHR = FileAPI.upload(config);
}, 1);
} else {
if (this.__origError) {
throw this.__origError;
}
orig.apply(xhr, arguments);
}
};
});
window.XMLHttpRequest.__isFileAPIShim = true;
window.FormData = FormData = function () {
return {
append: function (key, val, name) {
if (val.__isFileAPIBlobShim) {
val = val.data[0];
}
this.data.push({
key: key,
val: val,
name: name
});
},
data: [],
__isFileAPIShim: true
};
};
window.Blob = Blob = function (b) {
return {
data: b,
__isFileAPIBlobShim: true
};
};
}
})();
(function () {
/** @namespace FileAPI.forceLoad */
/** @namespace window.FileAPI.jsUrl */
/** @namespace window.FileAPI.jsPath */
function isInputTypeFile(elem) {
return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';
}
function hasFlash() {
try {
var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
if (fo) return true;
} catch (e) {
if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true;
}
return false;
}
function getOffset(obj) {
var left = 0, top = 0;
if (window.jQuery) {
return jQuery(obj).offset();
}
if (obj.offsetParent) {
do {
left += (obj.offsetLeft - obj.scrollLeft);
top += (obj.offsetTop - obj.scrollTop);
obj = obj.offsetParent;
} while (obj);
}
return {
left: left,
top: top
};
}
if (FileAPI.shouldLoad) {
FileAPI.hasFlash = hasFlash();
//load FileAPI
if (FileAPI.forceLoad) {
FileAPI.html5 = false;
}
if (!FileAPI.upload) {
var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src;
if (window.FileAPI.jsUrl) {
jsUrl = window.FileAPI.jsUrl;
} else if (window.FileAPI.jsPath) {
basePath = window.FileAPI.jsPath;
} else {
for (i = 0; i < allScripts.length; i++) {
src = allScripts[i].src;
index = src.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/);
if (index > -1) {
basePath = src.substring(0, index + 1);
break;
}
}
}
if (FileAPI.staticPath == null) FileAPI.staticPath = basePath;
script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js');
document.getElementsByTagName('head')[0].appendChild(script);
}
FileAPI.ngfFixIE = function (elem, fileElem, changeFn) {
if (!hasFlash()) {
throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';
}
var fixInputStyle = function () {
var label = fileElem.parent();
if (elem.attr('disabled')) {
if (label) label.removeClass('js-fileapi-wrapper');
} else {
if (!fileElem.attr('__ngf_flash_')) {
fileElem.unbind('change');
fileElem.unbind('click');
fileElem.bind('change', function (evt) {
fileApiChangeFn.apply(this, [evt]);
changeFn.apply(this, [evt]);
});
fileElem.attr('__ngf_flash_', 'true');
}
label.addClass('js-fileapi-wrapper');
if (!isInputTypeFile(elem)) {
label.css('position', 'absolute')
.css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px')
.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
.css('filter', 'alpha(opacity=0)').css('display', elem.css('display'))
.css('overflow', 'hidden').css('z-index', '900000')
.css('visibility', 'visible');
fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px')
.css('position', 'absolute').css('top', '0px').css('left', '0px');
}
}
};
elem.bind('mouseenter', fixInputStyle);
var fileApiChangeFn = function (evt) {
var files = FileAPI.getFiles(evt);
//just a double check for #233
for (var i = 0; i < files.length; i++) {
if (files[i].size === undefined) files[i].size = 0;
if (files[i].name === undefined) files[i].name = 'file';
if (files[i].type === undefined) files[i].type = 'undefined';
}
if (!evt.target) {
evt.target = {};
}
evt.target.files = files;
// if evt.target.files is not writable use helper field
if (evt.target.files !== files) {
evt.__files_ = files;
}
(evt.__files_ || evt.target.files).item = function (i) {
return (evt.__files_ || evt.target.files)[i] || null;
};
};
};
FileAPI.disableFileInput = function (elem, disable) {
if (disable) {
elem.removeClass('js-fileapi-wrapper');
} else {
elem.addClass('js-fileapi-wrapper');
}
};
}
})();
if (!window.FileReader) {
window.FileReader = function () {
var _this = this, loadStarted = false;
this.listeners = {};
this.addEventListener = function (type, fn) {
_this.listeners[type] = _this.listeners[type] || [];
_this.listeners[type].push(fn);
};
this.removeEventListener = function (type, fn) {
if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1);
};
this.dispatchEvent = function (evt) {
var list = _this.listeners[evt.type];
if (list) {
for (var i = 0; i < list.length; i++) {
list[i].call(_this, evt);
}
}
};
this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null;
var constructEvent = function (type, evt) {
var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error};
if (evt.result != null) e.target.result = evt.result;
return e;
};
var listener = function (evt) {
if (!loadStarted) {
loadStarted = true;
if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt));
}
var e;
if (evt.type === 'load') {
if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt));
e = constructEvent('load', evt);
if (_this.onload) _this.onload(e);
_this.dispatchEvent(e);
} else if (evt.type === 'progress') {
e = constructEvent('progress', evt);
if (_this.onprogress) _this.onprogress(e);
_this.dispatchEvent(e);
} else {
e = constructEvent('error', evt);
if (_this.onerror) _this.onerror(e);
_this.dispatchEvent(e);
}
};
this.readAsDataURL = function (file) {
FileAPI.readAsDataURL(file, listener);
};
this.readAsText = function (file) {
FileAPI.readAsText(file, listener);
};
};
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
Package.describe({
name: "danialf:ng-file-upload",
"version": "12.2.11",
summary: "Lightweight Angular directive to upload files with optional FileAPI shim for cross browser support",
git: "https://github.com/danialfarid/ng-file-upload.git"
});
Package.onUse(function (api) {
api.use('angular:angular@1.2.0', 'client');
api.addFiles('ng-file-upload-all.js', 'client');
});
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html ng-app="infoAdmin">
<head>
<meta charset="utf-8">
<title>Infoscreen admin</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.js"></script>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"></link>
<script src="/static/js/ng-file-upload-bower-12.2.11/ng-file-upload-all.js"></script>
<script src="/static/js/infoadmin_controllers.js"></script>
</head>
<body>
<div class="container" ng-include="'/static/html/infoscreen_admin.html'"></div>
</body>
</html>
+117 -3
View File
@@ -1,14 +1,25 @@
from django.shortcuts import render
from django.http import HttpResponse
from django.http import HttpResponse,HttpResponseBadRequest
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_http_methods
from infoscreen.models import ABBJob, Rotation
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.decorators import permission_required
from django.utils import timezone
from infoscreen.models import ABBJob, Rotation, InfoItem, InfoInstance
from infoscreen.models import ABBInfoItem, ExternalImageInfoItem, ImageInfoItem, SossoInfoItem
from infoscreen.models import ImageUploadForm
from datetime import datetime, timedelta
import json
def index(request,idx, *args, **kwargs):
return render(request, 'infoscreen_index.html',{'rotation':idx})
@permission_required('infoscreen.change_infoinstance', login_url='/login')
def admin(request, *args, **kwargs):
return render(request,'infoscreen_admin.html',{})
def default(request,*args,**kwargs):
try:
first = Rotation.objects.all()[0].id
@@ -19,7 +30,7 @@ def default(request,*args,**kwargs):
# send abb jobs which have been created less than month ago
@require_http_methods(["GET"])
def abb_job_list(request, *args, **kwargs):
limit = timezone.now() - timedelta(days=30)
limit = timezone.now() - timedelta(days=60)
jobs = ABBJob.objects.filter(created__gt=limit)
joblist = list(map(lambda j:j.get_dict(), jobs))
return HttpResponse(json.dumps(joblist))
@@ -34,3 +45,106 @@ def rotation(request, idx, *args, **kwargs):
return resp
return HttpResponse(json.dumps(rotation.get_dict()))
def itemCreator(model):
@ensure_csrf_cookie
@require_http_methods(["POST"])
@permission_required('infoscreen.change_infoinstance', login_url='/login')
def createItem(request,*args, **kwargs):
try:
data = json.loads(request.body.decode("utf-8"))
except ValueError:
return HttpResponseBadRequest('{"status":"failure","error":"invalid json supplied"}')
try:
model.create_from_dict(data)
return HttpResponse('{"status":"success"}')
except RuntimeError as e:
return HttpResponseBadRequest('{"status":"failure","error":"{}"}'.format(str(e)))
return createItem
def itemDeletor(model):
@ensure_csrf_cookie
@require_http_methods(["DELETE"])
@permission_required('infoscreen.change_infoinstance', login_url='/login')
def deleteItem(request,*args, **kwargs):
idx = kwargs.pop("idx", 0)
try:
item = model.objects.get(pk=idx)
except model.DoesNotExist:
resp = HttpResponse('{"error":"object not found"}')
resp.status_code = 404
return resp
try:
item.delete()
return HttpResponse('{"status":"success"}')
except:
resp = HttpResponse('{"error" : "could not delete item"}')
resp.status_code = 500
return resp
return deleteItem
# due to model structure this is little complicated
@ensure_csrf_cookie
@permission_required('infoscreen.change_infoinstance', login_url='/login')
@require_http_methods(["DELETE"])
def deleteInfoItem(request,*args, **kwargs):
type_id = kwargs.pop("type_id", 0)
idx = kwargs.pop("idx", 0)
if True:
ct = ContentType.objects.get_for_id(type_id)
item = ct.get_object_for_this_type(pk=idx)
else:
resp = HttpResponse('{"error":"object not found"}')
resp.status_code = 404
return resp
try:
item.delete()
return HttpResponse('{"status":"success"}')
except:
resp = HttpResponse('{"error" : "could not delete item"}')
resp.status_code = 500
return resp
@require_http_methods(["GET"])
def rotations(request,*args,**kwargs):
rotations = list(map(lambda r: r.get_list(),Rotation.objects.all()))
return HttpResponse(json.dumps(rotations))
@require_http_methods(["GET"])
def infoTypes(request, *args, **kwargs):
types = []
classes = InfoItem.get_subclasses()
for c in classes:
types.append({
"name":c.__name__,
"create_template_url":c.get_create_template_url(),
})
return HttpResponse(json.dumps(types))
def infoItems(request, *args, **kwargs):
items = []
classes = InfoItem.get_subclasses()
for c in classes:
for i in c.objects.all():
items.append(i.get_dict())
return HttpResponse(json.dumps(items))
@require_http_methods(["POST"])
@ensure_csrf_cookie
@permission_required('infoscreen.change_infoinstance', login_url='/login')
def createImageItem(request, *args, **kwargs):
form = ImageUploadForm(request.POST,request.FILES)
if not form.is_valid():
return HttpResponseBadRequest('{"status":"failure","error":"invalid data supplied"}')
img = form.cleaned_data['image']
name = form.cleaned_data['name']
ImageInfoItem.objects.create(img=img, name=name)
return HttpResponse('{"status":"success"}')
createInstance = itemCreator(InfoInstance)
deleteInstance = itemDeletor(InfoInstance)
createABBItem = itemCreator(ABBInfoItem)
createSossoItem = itemCreator(SossoInfoItem)
createExternalImageInfoItem = itemCreator(ExternalImageInfoItem)
+20
View File
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-10-04 12:40
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0004_auto_20160915_1606'),
]
operations = [
migrations.AddField(
model_name='member',
name='paid',
field=models.DateTimeField(blank=True, default=None, null=True),
),
]
+27 -8
View File
@@ -1,5 +1,6 @@
from django.db import models
from django.utils import timezone
from datetime import datetime
from io import StringIO
import csv
@@ -15,6 +16,7 @@ class Member(models.Model):
AYY = models.BooleanField(default=False)
jas = models.BooleanField(default=False)
created = models.DateTimeField(default=timezone.now)
paid = models.DateTimeField(default=None,null=True,blank=True)
def get_dict(self):
return {
@@ -25,7 +27,8 @@ class Member(models.Model):
'POR':self.POR,
'AYY':self.AYY,
'jas':self.jas,
'created':self.created.isoformat(' '),
'created':date2str(self.created),
'paid':date2str(self.paid),
}
@classmethod
@@ -36,16 +39,17 @@ class Member(models.Model):
def update_from_dict(self,d):
dmap = {
'first_name': 'first_name',
'last_name': 'last_name',
'email': 'email',
'POR': 'POR',
'AYY': 'AYY',
'jas': 'jas',
'first_name': reverseMap('first_name'),
'last_name': reverseMap('last_name'),
'email': reverseMap('email'),
'POR': reverseMap('POR'),
'AYY': reverseMap('AYY'),
'jas': reverseMap('jas'),
'paid': reverseMap('paid',str2date),
}
for k,v in d.items():
try:
self.__setattr__(dmap[k],v)
self.__setattr__(dmap[k].key,dmap[k].parser(v))
except KeyError:
pass
self.save()
@@ -66,6 +70,7 @@ class Member(models.Model):
POR=row[3],
AYY=row[4].lower() in ['yes','y','1','true',"kyllä", "khyl"],
jas=row[5].lower() in ['yes','y','1','true',"kyllä", "khyl"],
paid = timezone.now()
)
print("added obj {}".format(obj))
except:
@@ -85,3 +90,17 @@ class MemberRequest(models.Model):
'id': self.id,
'member': self.member.get_dict(),
}
def date2str(date):
if not date:
return 'Ei koskaan'
return date.strftime("%Y-%m-%d %H:%M:%S")
def str2date(s):
return datetime.strptime(s,"%Y-%m-%d %H:%M:%S")
class reverseMap:
def __init__(self, key, parser=lambda x:x):
self.key = key
self.parser = parser
+5
View File
@@ -10,6 +10,7 @@
<th>JAS-listalla</th>
<th>Asuinpaikka</th>
<th>Lisätty</th>
<th>Maksanut</th>
</tr>
</thead>
<tbody>
@@ -21,6 +22,10 @@
<td>{{ x.jas }}</td>
<td>{{ x.POR }}</td>
<td>{{ x.created }}</td>
<td>{{ x.paid }}</td>
<td>
<input type="button" value="Päivitä maksu" class="btn btn-success" ng-click="updatePayment(x.id)" />
</td>
<td>
<a href="#/edit/{{x.id}}"<input type="button" value="Edit" class="btn btn-info">Muokkaa</input></a>
<input type="button" value="Poista" class="btn btn-danger" confirmed-click="delete_member(x.id)" ng-confirm-click="Are you sure?"/>
+6 -1
View File
@@ -78,6 +78,11 @@ app.controller("getController", function($scope, $http, $window, $location){
});
};
$scope.getFunction();
$scope.updatePayment= function(id){
$http.put("/members/api/member/"+id,{paid:moment().format("YYYY-MM-DD kk:mm:ss") }).then(function(resp){
$scope.getFunction();
});
}
$scope.delete_member = function(id) {
$http.delete("/members/api/member/" + id).then(
function(response) {
@@ -155,4 +160,4 @@ app.controller("addManyController", function($scope, $http, $window) {
}
);
};
});
});
File diff suppressed because it is too large Load Diff
+1
View File
@@ -20,6 +20,7 @@
<script src="/static/js/appconfig.js"></script>
<script src="/static/js/members_routers.js"></script>
<script src="/static/js/members_controllers.js"></script>
<script src="/static/js/moment.js"></script>
<link href="/static/css/simple-sidebar.css" rel="stylesheet">
</head>
+25 -3
View File
@@ -34,6 +34,17 @@ from infoscreen.views import index as infoindex
from infoscreen.views import default as infodefault
from infoscreen.views import abb_job_list
from infoscreen.views import rotation
from infoscreen.views import rotations
from infoscreen.views import infoItems
from infoscreen.views import infoTypes
from infoscreen.views import deleteInfoItem
from infoscreen.views import createInstance as createInfoInstance
from infoscreen.views import deleteInstance as deleteInfoInstance
from infoscreen.views import createExternalImageInfoItem
from infoscreen.views import createImageItem
from infoscreen.views import createABBItem
from infoscreen.views import createSossoItem
from infoscreen.views import admin as infoscreen_admin
#application
from members.views import applicationindex
@@ -58,7 +69,18 @@ urlpatterns = [
url(r'^infoscreen/$', infodefault),
url(r'^infoscreen/(?P<idx>\d+)$', infoindex),
url(r'^infoscreen/abbjobs$', abb_job_list),
url(r'^infoscreen/rotation/(?P<idx>\d+)$', rotation),
#application
url(r'^application/$', applicationindex),
url(r'^infoscreen/items$', infoItems),
url(r'^infoscreen/rotation/(?P<idx>\d+)$', rotation),
url(r'^infoscreen/rotations$', rotations),
url(r'^infoscreen/instance$', createInfoInstance),
url(r'^infoscreen/instance/(?P<idx>\d+)$', deleteInfoInstance),
url(r'^infoscreen/types$', infoTypes),
url(r'^infoscreen/delete_item/(?P<type_id>\d+)/(?P<idx>\d+)$', deleteInfoItem),
url(r'^infoscreen/create_external_image$', createExternalImageInfoItem),
url(r'^infoscreen/create_image$', createImageItem),
url(r'^infoscreen/create_abbitem$', createABBItem),
url(r'^infoscreen/create_sossoitem$', createSossoItem),
url(r'^infoscreen/admin$', infoscreen_admin),
#application
url(r'^application/$', applicationindex),
]