GCal Coupling: Management Site - decoupled Email from profile

This commit is contained in:
Martin Bauer 2014-06-20 12:24:01 +02:00
parent 6431764858
commit 290db91956
16 changed files with 410 additions and 29 deletions

View File

@ -1 +1 @@
{"_module": "oauth2client.client", "token_expiry": "2014-04-26T10:33:34Z", "access_token": "ya29.1.AADtN_WzqSiT0Ir0jy8f_InaX_NAUAs98E5YwU_uHYMMCvhfc90boeQSVPQpJQTWHw", "token_uri": "https://accounts.google.com/o/oauth2/token", "invalid": false, "token_response": {"access_token": "ya29.1.AADtN_WzqSiT0Ir0jy8f_InaX_NAUAs98E5YwU_uHYMMCvhfc90boeQSVPQpJQTWHw", "token_type": "Bearer", "expires_in": 3600}, "client_id": "34462582242-4kpdvvbi27ajt4u22uitqurpve9o8ipj.apps.googleusercontent.com", "id_token": null, "client_secret": "y4t9XBrJdCODPTO5UvtONWWn", "revoke_uri": "https://accounts.google.com/o/oauth2/revoke", "_class": "OAuth2Credentials", "refresh_token": "1/7-6-m_lLAKX8IeD7OuGtkcIiprty_nZUSxhMunSC5b0", "user_agent": null}
{"_module": "oauth2client.client", "token_expiry": "2014-06-20T09:36:07Z", "access_token": "ya29.LgA4Q4jhQqjEoBwAAABv_luKCAgrb2C-s1IcYmGZ8nZViS_QHvHKT-IkSo31RQ", "token_uri": "https://accounts.google.com/o/oauth2/token", "invalid": false, "token_response": {"access_token": "ya29.LgA4Q4jhQqjEoBwAAABv_luKCAgrb2C-s1IcYmGZ8nZViS_QHvHKT-IkSo31RQ", "token_type": "Bearer", "expires_in": 3600}, "client_id": "34462582242-4kpdvvbi27ajt4u22uitqurpve9o8ipj.apps.googleusercontent.com", "id_token": null, "client_secret": "y4t9XBrJdCODPTO5UvtONWWn", "revoke_uri": "https://accounts.google.com/o/oauth2/revoke", "_class": "OAuth2Credentials", "refresh_token": "1/7-6-m_lLAKX8IeD7OuGtkcIiprty_nZUSxhMunSC5b0", "user_agent": null}

View File

@ -12,8 +12,7 @@ class ParticipationSerializer(serializers.ModelSerializer):
def get_identity(self, data):
""" This hook is required for bulk update. """
try:
print "get_identity event:" + str( data.get('event', None) ) + " user " + str( data.get('user') )
return ( data.get('event', None), data.get('user') )
return data.get('event', None), data.get('user')
except AttributeError:
return None

View File

@ -4,11 +4,12 @@ import datetime
import time
from eventplanner.models import Event, EventParticipation
from eventplanner_gcal.models import GCalMapping, GCalPushChannel
from eventplanner_gcal.models import GCalMapping, GCalPushChannel,UserGCalCoupling
from apiclient.http import BatchHttpRequest
from django.contrib.auth.models import User
from django.conf import settings
from pprint import pprint
logger = logging.getLogger(__name__)
@ -54,15 +55,16 @@ def buildGCalAttendeesObj( event ):
"""Builds a attendees object that is inserted into the GCal event.
Attendees are all users that have a google mail address. """
result = []
for u in User.objects.all():
if u.email.endswith( "@gmail.com") or u.email.endswith("@googlemail.com"):
for userMapping in UserGCalCoupling.objects.all():
u = userMapping.user
participation = EventParticipation.get_or_create( u, event )
status = "tentative"
if participation.status == 'Yes': status = "accepted"
if participation.status == 'No' : status = "declined"
o = {
'id': u.email,
'id': userMapping.email,
'email': u.email,
'displayName': u.username,
'comment': participation.comment,
@ -222,6 +224,9 @@ def syncFromLocalToGoogle( service = None ):
eventDjangoID = int( gcalEv['extendedProperties']['private']['blechreizID'] )
try:
djangoEv = Event.objects.get( pk=eventDjangoID )
if 'attendees' not in gcalEv:
gcalEv['attendees'] = []
if gcalEv['attendees'] != buildGCalAttendeesObj( djangoEv ):
batch.add( updateGCalEvent( service, djangoEv ) )
batchIsEmpty = False

View File

@ -1,6 +1,8 @@
import logging
import uuid
from eventplanner.models import Event
from django.contrib.auth.models import User
from apiclient.channel import Channel
from django.db import models
@ -9,6 +11,12 @@ from django.db import models
logger = logging.getLogger(__name__)
class UserGCalCoupling( models.Model ):
# For every user in this table the gcal coupling is activated
user = models.OneToOneField( User )
email = models.CharField( max_length=1024 )
class GCalMapping( models.Model ):
"""Mapping between event id at google and local event id"""
gcal_id = models.CharField( max_length=64 )

View File

@ -9,10 +9,10 @@ import logging
logger = logging.getLogger( __name__ )
@receiver( post_save, sender=User )
def user_changed( **kwargs ):
logger.info("Synchronizing with google - user information changed")
syncFromLocalToGoogle( getServiceObject() )
#@receiver( post_save, sender=User )
#def user_changed( **kwargs ):
# logger.info("Synchronizing with google - user information changed")
# syncFromLocalToGoogle( getServiceObject() )
@receiver( post_save,sender= Event)

View File

@ -0,0 +1,184 @@
/* ========================================================================
* bootstrap-switch - v2.0.1
* http://www.bootstrap-switch.org
* ========================================================================
* Copyright 2012-2013 Mattia Larentis
*
* ========================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================================
*/
.has-switch {
display: inline-block;
cursor: pointer;
border-radius: 4px;
border: 1px solid;
border-color: #cccccc;
position: relative;
text-align: left;
overflow: hidden;
line-height: 8px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
vertical-align: middle;
min-width: 100px;
-webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
}
.has-switch:focus {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
}
.has-switch.switch-mini {
min-width: 72px;
}
.has-switch.switch-mini span,
.has-switch.switch-mini label {
padding-bottom: 4px;
padding-top: 4px;
font-size: 10px;
line-height: 9px;
}
.has-switch.switch-mini i.switch-mini-icons {
height: 1.20em;
line-height: 9px;
vertical-align: text-top;
text-align: center;
transform: scale(0.6);
margin-top: -1px;
margin-bottom: -1px;
}
.has-switch.switch-small {
min-width: 80px;
}
.has-switch.switch-small span,
.has-switch.switch-small label {
padding-bottom: 3px;
padding-top: 3px;
font-size: 12px;
line-height: 18px;
}
.has-switch.switch-large {
min-width: 120px;
}
.has-switch.switch-large span,
.has-switch.switch-large label {
padding-bottom: 9px;
padding-top: 9px;
font-size: 16px;
line-height: normal;
}
.has-switch.switch-animate > div {
-webkit-transition: left 0.5s;
transition: left 0.5s;
}
.has-switch.switch-off > div {
left: -50%;
}
.has-switch.switch-on > div {
left: 0%;
}
.has-switch.disabled {
opacity: 0.5;
filter: alpha(opacity=50);
cursor: default !important;
}
.has-switch.disabled span,
.has-switch.disabled label {
cursor: default !important;
}
.has-switch > div {
display: inline-block;
width: 150%;
position: relative;
top: 0;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
.has-switch input[type=radio],
.has-switch input[type=checkbox] {
display: none;
}
.has-switch span,
.has-switch label {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
cursor: pointer;
position: relative;
display: inline-block !important;
height: 100%;
padding-bottom: 4px;
padding-top: 4px;
font-size: 14px;
line-height: 20px;
}
.has-switch label {
text-align: center;
margin-top: -1px;
margin-bottom: -1px;
z-index: 100;
width: 33.333333333%;
background: #ffffff;
}
.has-switch label i {
color: #000;
text-shadow: 0 1px 0 #fff;
line-height: 18px;
pointer-events: none;
}
.has-switch span {
text-align: center;
z-index: 1;
width: 33.333333333%;
}
.has-switch span.switch-left {
color: #f00;
border-bottom-left-radius: 4px;
border-top-left-radius: 4px;
}
.has-switch span.switch-right {
color: #000;
background: #eeeeee;
}
.has-switch span.switch-primary,
.has-switch span.switch-left {
color: #fff;
background: #428bca;
}
.has-switch span.switch-info {
color: #fff;
background: #5bc0de;
}
.has-switch span.switch-success {
color: #fff;
background: #5cb85c;
}
.has-switch span.switch-warning {
background: #f0ad4e;
color: #fff;
}
.has-switch span.switch-danger {
color: #fff;
background: #d9534f;
}
.has-switch span.switch-default {
color: #000;
background: #eeeeee;
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,110 @@
{% extends "website/base.html" %}
{% load sekizai_tags staticfiles %}
{% block content %}
{% addtoblock "css" strip %}
<link rel="stylesheet" href="{{STATIC_URL}}/css/bootstrap-switch.min.css" type="text/css" media="screen" />
{% endaddtoblock %}
{% addtoblock "css" %}
<style>
h5 {
padding-top: 20px;
}
#errorlabel {
color: red;
margin: 10px;
font-size: 12px;
}
#activate {
margin-top: 30px;
}
</style>
{% endaddtoblock %}
{% addtoblock "js" strip %}
<script src="{{STATIC_URL}}/js/bootstrap-switch.min.js"></script>
{% endaddtoblock %}
{% addtoblock "js" %}
<script>
$("[name='my-checkbox']").bootstrapSwitch();
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
validateEmail = function() {
if ( endsWith( $("#id_email").val(), "@gmail.com") ||
endsWith( $("#id_email").val(), "@googlemail.com") ) {
// Activate submit button
$("#activate").prop("disabled", false);
$("#errorlabel").html("");
} else {
$("#activate").prop("disabled", true);
$("#errorlabel").html("Erlaubte Endung: <em>@gmail.com</em> oder<em>@googlemail.com</em>");
}
}
$("#id_email").change( validateEmail );
validateEmail();
</script>
{% endaddtoblock %}
<div class="container">
<div class="row">
<div class="span12">
<h2>Google Kalender Anbindung</h2>
</div>
<div class="span6">
<p>
<h5>NEU</h5>
Die Blechreiz Termine können jetzt automatisch in den eigenen Google Kalender übernommen werden.
Auch vom Google Kalender aus kann man sich dann für Termine eintragen, direkt vom Handy oder Tablet aus.
</p>
<p>
<h5>SO GEHTS:</h5>
Einfach die eigene Google Mail Adresse angeben und die Kopplung aktivieren.
</p>
<h5>KOPPLUNG:</h5>
<form method="POST" >
{% csrf_token %}
{% if enabled %}
Kopplung aktiviert für <em>{{ mail }}</em>.
<input type="hidden" name="activate" value="0" >
<br><br>
<input class="btn btn-primary" value="Deaktivieren" type="submit">
{% else %}
<input type="hidden" name="activate" value="1" >
<label for="id_email">Email:</label>
<input id="id_email" type="text" placeholder="GMail Adresse" name="email">
<br>
<span id="errorlabel"></span>
<br>
<input id="activate" class="btn btn-primary" value="Aktivieren" type="submit">
{% endif %}
</form>
</div>
<div class="span3 offset1">
<img src="{{STATIC_URL}}/img/google_cal.png">
</div>
</div>
</div>
{% endblock %}

View File

@ -1,9 +1,10 @@
from django.conf.urls import patterns, url
from views import runSync, gcalApiCallback
from views import runSync, gcalApiCallback, manage
urlpatterns = patterns('',
url(r'^runSync$', runSync ),
url(r'^gcalApiCallback$', gcalApiCallback ),
url(r'^manage$', manage ),
)

View File

@ -4,6 +4,8 @@ from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from pprint import pformat
from eventplanner_gcal.google_sync import checkIfGoogleCallbackIsValid
from eventplanner_gcal.models import UserGCalCoupling
from django.shortcuts import render
import logging
@ -14,6 +16,29 @@ def runSync( request ):
syncFromLocalToGoogle()
return redirect("/")
def manage( request ):
if request.method == 'POST':
if request.POST['activate'] == "1":
UserGCalCoupling.objects.filter( user=request.user ).delete()
c = UserGCalCoupling( user=request.user, email = request.POST['email'] )
c.save()
syncFromLocalToGoogle()
else:
UserGCalCoupling.objects.filter( user=request.user ).delete()
syncFromLocalToGoogle()
context = {}
userCoupling = UserGCalCoupling.objects.filter( user = request.user )
context['enabled'] = len(userCoupling)
assert( len(userCoupling) < 2 )
if len(userCoupling) == 1:
context['mail'] = userCoupling[0].email
return render( request, 'eventplanner_gcal/management.html', context )
@csrf_exempt
def gcalApiCallback( request ):
# TODO check channel info here

View File

@ -43,6 +43,11 @@
if ( data['redirect']) {
window.location.href = data['redirect'];
}
},
error: function( jqXHR, text1, text2 ) {
console.log( jqXHR );
console.log( text1 );
console.log( text2 );
}
});
e.preventDefault();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -48,6 +48,7 @@
</a>
<ul class="dropdown-menu">
<li><a href="/musicians/profile">Eigenes Profil</a></li>
<li><a href="/eventplanner_gcal/manage">Google Kalender</a></li>
<li><a href="/musicians/changePassword">Passwort ändern</a> </li>
<li><a href="/musicians/logout"> Logout</a></li>
</ul>

View File

@ -76,13 +76,12 @@
<section id="feature_slider" class="">
<article class="slide" id="slide_news"
style="background: url('{{STATIC_URL}}/img/slides/osterhase.png') repeat-x top center;">
<article class="slide" id="slide_news" style="background: url('{{STATIC_URL}}/img/slides/aqua.jpg') repeat-x top center;">
<img class="asset left-30 sp600 t150 z1" src="{{STATIC_URL}}/img/googleCalNew.png" />
<img class="asset left-30 sp600 t150 z1" src="{{STATIC_URL}}/img/google_cal.png" />
<div class="info">
<a href="/musicians/profile">GoogleCalendar</a>
<div class="subtitle subtitlebg">Jetzt auch auf dem Handy im GoogleCalendar Termine planen: Einfach im Profil eine Google Email Adresse eintragen...</div>
<a href="/eventplanner_gcal/manage">GoogleCalendar</a>
<div class="subtitle subtitlebg">Jetzt auch auf dem Handy im Google Kalender Termine planen</div>
</div>
</article>