Merge branch 'develop' of sika:vtmk/web2.0 into develop
This commit is contained in:
+4
-3
@@ -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',
|
||||
),
|
||||
]
|
||||
+58
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
}]);
|
||||
@@ -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
+6
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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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?"/>
|
||||
|
||||
@@ -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
@@ -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
@@ -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),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user