From ad50acb21488499cdca2fe3935a4c772777a9c2c Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Fri, 14 Oct 2016 12:37:20 +1100 Subject: [PATCH 01/13] WordPress plugin WIP, authentication working but doens't fetch data --- .env.example | 5 ++ clatoolkit_project/common/CLRecipe.py | 1 + .../dataintegration/core/plugins/base.py | 2 +- .../migrations/0001_initial.py | 11 +++ clatoolkit_project/dataintegration/models.py | 42 ++++++---- .../plugins/wordpress/__init__.py | 0 .../plugins/wordpress/cladi_plugin.py | 78 +++++++++++++++++++ clatoolkit_project/dataintegration/urls.py | 2 + clatoolkit_project/dataintegration/views.py | 10 ++- requirements.txt | 5 +- 10 files changed, 136 insertions(+), 20 deletions(-) create mode 100644 clatoolkit_project/dataintegration/plugins/wordpress/__init__.py create mode 100644 clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py diff --git a/.env.example b/.env.example index 9f6a944..ef3fbdf 100644 --- a/.env.example +++ b/.env.example @@ -48,6 +48,11 @@ TWITTER_APP_SECRET= TWITTER_OAUTH_TOKEN= TWITTER_OAUTH_TOKEN_SECRET= +# WordPress +WORDPRESS_ROOT= +WORDPRESS_KEY= +WORDPRESS_SECRET= + # YouTube YOUTUBE_CLIENT_ID= YOUTUBE_CLIENT_SECRET= \ No newline at end of file diff --git a/clatoolkit_project/common/CLRecipe.py b/clatoolkit_project/common/CLRecipe.py index 621cef0..359616b 100644 --- a/clatoolkit_project/common/CLRecipe.py +++ b/clatoolkit_project/common/CLRecipe.py @@ -12,6 +12,7 @@ class CLRecipe(object): PLATFORM_BLOG = 'Blog' PLATFORM_GITHUB = 'GitHub' PLATFORM_TRELLO = 'Trello' + PLATFORM_WORDPRESS = "WordPress" # Verbs VERB_CREATED = 'created' diff --git a/clatoolkit_project/dataintegration/core/plugins/base.py b/clatoolkit_project/dataintegration/core/plugins/base.py index ec1957d..7d5dede 100644 --- a/clatoolkit_project/dataintegration/core/plugins/base.py +++ b/clatoolkit_project/dataintegration/core/plugins/base.py @@ -17,7 +17,7 @@ class DIBasePlugin(object): config_json_keys = [] - def perform_import(self, retrieval_param, course_code): + def perform_import(self, retrieval_param, unit): """Called to start the import from api for a plugin. diff --git a/clatoolkit_project/dataintegration/migrations/0001_initial.py b/clatoolkit_project/dataintegration/migrations/0001_initial.py index 5f89d96..715e892 100644 --- a/clatoolkit_project/dataintegration/migrations/0001_initial.py +++ b/clatoolkit_project/dataintegration/migrations/0001_initial.py @@ -2,11 +2,13 @@ from __future__ import unicode_literals from django.db import models, migrations +import django_pgjson.fields class Migration(migrations.Migration): dependencies = [ + ('clatoolkit', '0001_initial'), ] operations = [ @@ -25,6 +27,15 @@ class Migration(migrations.Migration): ('updatedAt', models.CharField(max_length=30)), ], ), + migrations.CreateModel( + name='PlatformConfig', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('platform', models.CharField(max_length=50)), + ('config', django_pgjson.fields.JsonField()), + ('unit', models.ForeignKey(to='clatoolkit.UnitOffering')), + ], + ), migrations.CreateModel( name='Video', fields=[ diff --git a/clatoolkit_project/dataintegration/models.py b/clatoolkit_project/dataintegration/models.py index 1aae22e..baeeb80 100644 --- a/clatoolkit_project/dataintegration/models.py +++ b/clatoolkit_project/dataintegration/models.py @@ -1,24 +1,34 @@ from django.db import models +from django_pgjson.fields import JsonField +from clatoolkit.models import UnitOffering + # Create your models here. class Comment(models.Model): - commId = models.CharField(max_length=100) - authorDispName = models.CharField(max_length=1000) - text = models.CharField(max_length=10000) - videoId = models.CharField(max_length=20) - videoUrl = models.CharField(max_length=1000) - parentId = models.CharField(max_length=50) - parentUsername = models.CharField(max_length=1000, blank=True) - isReply = models.BooleanField() - updatedAt = models.CharField(max_length=30) + commId = models.CharField(max_length=100) + authorDispName = models.CharField(max_length=1000) + text = models.CharField(max_length=10000) + videoId = models.CharField(max_length=20) + videoUrl = models.CharField(max_length=1000) + parentId = models.CharField(max_length=50) + parentUsername = models.CharField(max_length=1000, blank=True) + isReply = models.BooleanField() + updatedAt = models.CharField(max_length=30) + class Video(models.Model): - videoId = models.CharField(max_length=20) - videoUrl = models.CharField(max_length=1000) - videoTitle = models.CharField(max_length=300) - channelId = models.CharField(max_length=30) - channelUrl = models.CharField(max_length=1000) - #videoList = models.ManyToManyField('self') - #commentList = models.ManyToManyField(Comment) + videoId = models.CharField(max_length=20) + videoUrl = models.CharField(max_length=1000) + videoTitle = models.CharField(max_length=300) + channelId = models.CharField(max_length=30) + channelUrl = models.CharField(max_length=1000) + # videoList = models.ManyToManyField('self') + # commentList = models.ManyToManyField(Comment) + + +class PlatformConfig(models.Model): + unit = models.ForeignKey(UnitOffering) + platform = models.CharField(max_length=50) + config = JsonField() diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/__init__.py b/clatoolkit_project/dataintegration/plugins/wordpress/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py new file mode 100644 index 0000000..caaad97 --- /dev/null +++ b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py @@ -0,0 +1,78 @@ +from django.http import HttpResponseRedirect, Http404 +from django.core.exceptions import PermissionDenied +from common.CLRecipe import CLRecipe +from dataintegration.core.plugins.base import DIBasePlugin, DIPluginDashboardMixin +from requests_oauthlib import OAuth1, OAuth1Session +from dataintegration.models import PlatformConfig +from clatoolkit.models import UnitOffering, UnitOfferingMembership +import requests +import os + + +class WordPressPlugin(DIBasePlugin, DIPluginDashboardMixin): + platform = CLRecipe.PLATFORM_WORDPRESS + + xapi_verbs = ['created', 'commented'] + xapi_objects = ['Note'] + + user_api_association_name = "WP username" + + def __init__(self): + self.client_key = os.environ.get("WORDPRESS_KEY") + self.client_secret = os.environ.get("WORDPRESS_SECRET") + self.wp_root = os.environ.get("WORDPRESS_ROOT") + + def start_authentication(self, request): + request_token_url = "{}/oauth1/request".format(self.wp_root) + base_authorization_url = "{}/oauth1/authorize".format(self.wp_root) + callback_uri = "{}://{}{}authorize".format(request.scheme, request.get_host(), request.path) + + oauth = OAuth1Session(self.client_key, self.client_secret, callback_uri) + fetch_response = oauth.fetch_request_token(request_token_url) + + temp_token = fetch_response.get('oauth_token') + temp_secret = fetch_response.get('oauth_token_secret') + + # Save credentials to the session as they will need to be reused in the authorization step + request.session["wp_temp_token"] = temp_token + request.session["wp_temp_secret"] = temp_secret + request.session["wp_temp_unit"] = request.GET["unit"] + + authorization_url = oauth.authorization_url(base_authorization_url, oauth_callback=callback_uri) + + return HttpResponseRedirect(authorization_url) + + def authorize(self, request): + access_token_url = "{}/oauth1/access".format(self.wp_root) + + verifier = request.GET['oauth_verifier'] + + oauth = OAuth1Session(self.client_key, + client_secret=self.client_secret, + resource_owner_key=request.session["wp_temp_token"], + resource_owner_secret=request.session["wp_temp_secret"], + verifier=verifier) + oauth_tokens = oauth.fetch_access_token(access_token_url) + + config_dict = { + "access_token_key": oauth_tokens.get('oauth_token'), + "access_token_secret": oauth_tokens.get('oauth_token_secret') + } + + unit_id = request.session["wp_temp_unit"] + + try: + unit = UnitOffering.objects.get(id=unit_id) + except UnitOffering.DoesNotExist: + raise Http404 + + if UnitOfferingMembership.is_admin(request.user, unit): + config = PlatformConfig(unit=unit, config=config_dict, platform=self.platform) + config.save() + else: + raise PermissionDenied + + return HttpResponseRedirect("/") + + def perform_import(self, retrieval_param, unit): + pass diff --git a/clatoolkit_project/dataintegration/urls.py b/clatoolkit_project/dataintegration/urls.py index f49b05f..64e5ef0 100755 --- a/clatoolkit_project/dataintegration/urls.py +++ b/clatoolkit_project/dataintegration/urls.py @@ -23,4 +23,6 @@ url(r'^process_trello/$', views.process_trello, name='processtrello'), url(r'^refreshtrello/$', views.refreshtrello, name='refreshtrello'), #url(r'^ytAuthCallback/(?P\d+)/$', views.ytAuthCallback, name='ytAuthCallback'), + url(r'^wp_connect/authorize$', views.wp_authorize, name='wp_authorize'), + url(r'^wp_connect/$', views.wp_connect, name='wp_connect'), ) diff --git a/clatoolkit_project/dataintegration/views.py b/clatoolkit_project/dataintegration/views.py index 141eedb..819189a 100644 --- a/clatoolkit_project/dataintegration/views.py +++ b/clatoolkit_project/dataintegration/views.py @@ -5,7 +5,7 @@ from django.shortcuts import render, render_to_response from authomatic import Authomatic from authomatic.adapters import DjangoAdapter -from authomatic.providers import oauth2 +from authomatic.providers import oauth2, oauth1 from dashboard.utils import * from django.template import RequestContext from dataintegration.tasks import * @@ -19,6 +19,7 @@ from dataintegration.groupbuilder import * from dataintegration.core.processingpipeline import * from dataintegration.core.recipepermissions import * +from dataintegration.plugins.wordpress.cladi_plugin import WordPressPlugin from rest_framework.decorators import api_view @@ -449,3 +450,10 @@ def assigngroups(request): html_response.write('Groups Assigned') return html_response + +def wp_connect(request): + return WordPressPlugin().start_authentication(request) + + +def wp_authorize(request): + return WordPressPlugin().authorize(request) diff --git a/requirements.txt b/requirements.txt index 46ab475..5f64a9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,7 +25,7 @@ nose==1.3.7 numexpr==2.4.3 numpy==1.11.1 oauth2client==1.5.1 -oauthlib==0.7.2 +oauthlib==2.0.0 openpyxl==2.3.0 psycopg2==2.6 pyLDAvis==1.2.0 @@ -34,7 +34,8 @@ PyGithub==1.26.0 python-igraph==0.7.1.post6 redis==2.10.3 requests==2.7.0 -requests-oauthlib==0.5.0 +# Temporary workaround until my PR is accpeted, otherwise oauth with WP fails +-e git://github.com/tommarmstrong/requests-oauthlib@9159b5787c09acd4235da09528e84fd90eed3a37#egg=requests_oauthlib scikit-bio==0.2.3 scikit-learn==0.16.1 scipy==0.15.1 From 4264927c33b8b2023a7dc3c8f133cc3998ce5686 Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Mon, 17 Oct 2016 16:05:30 +1100 Subject: [PATCH 02/13] Gets data from WP --- .../plugins/wordpress/cladi_plugin.py | 13 ++++++++++++- clatoolkit_project/dataintegration/urls.py | 1 + clatoolkit_project/dataintegration/views.py | 5 +++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py index caaad97..110a59f 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py +++ b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py @@ -6,6 +6,7 @@ from dataintegration.models import PlatformConfig from clatoolkit.models import UnitOffering, UnitOfferingMembership import requests +import json import os @@ -75,4 +76,14 @@ def authorize(self, request): return HttpResponseRedirect("/") def perform_import(self, retrieval_param, unit): - pass + config = unit.platformconfig_set.get(platform=self.platform).config + oauth = OAuth1(client_key=self.client_key, + client_secret=self.client_secret, + resource_owner_key=config["access_token_key"], + resource_owner_secret=config["access_token_secret"]) + try: + r = requests.get("{}/wp-json/clatoolkit-wp/v1/posts".format(self.wp_root), auth=oauth) + + except Exception as e: + return e + return r.text diff --git a/clatoolkit_project/dataintegration/urls.py b/clatoolkit_project/dataintegration/urls.py index 64e5ef0..de09aa5 100755 --- a/clatoolkit_project/dataintegration/urls.py +++ b/clatoolkit_project/dataintegration/urls.py @@ -25,4 +25,5 @@ #url(r'^ytAuthCallback/(?P\d+)/$', views.ytAuthCallback, name='ytAuthCallback'), url(r'^wp_connect/authorize$', views.wp_authorize, name='wp_authorize'), url(r'^wp_connect/$', views.wp_connect, name='wp_connect'), + url(r'^wp_refresh/$', views.wp_refresh, name='wp_refresh'), ) diff --git a/clatoolkit_project/dataintegration/views.py b/clatoolkit_project/dataintegration/views.py index 819189a..4219308 100644 --- a/clatoolkit_project/dataintegration/views.py +++ b/clatoolkit_project/dataintegration/views.py @@ -457,3 +457,8 @@ def wp_connect(request): def wp_authorize(request): return WordPressPlugin().authorize(request) + + +def wp_refresh(request): + unit = UnitOffering.objects.get(id=request.GET["unit"]) + return HttpResponse(WordPressPlugin().perform_import(request, unit)) From 798d7489ad8b12df97c3a3c328e523205a5067b5 Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Tue, 18 Oct 2016 11:49:35 +1100 Subject: [PATCH 03/13] WordPress plugin can import posts and comments --- .../clatoolkit/migrations/0001_initial.py | 1 + clatoolkit_project/clatoolkit/models.py | 25 ++++++++- .../core/socialmediarecipebuilder.py | 8 +-- .../plugins/wordpress/cladi_plugin.py | 51 ++++++++++++++++--- clatoolkit_project/dataintegration/views.py | 3 +- 5 files changed, 72 insertions(+), 16 deletions(-) diff --git a/clatoolkit_project/clatoolkit/migrations/0001_initial.py b/clatoolkit_project/clatoolkit/migrations/0001_initial.py index e2aa03a..23868ef 100644 --- a/clatoolkit_project/clatoolkit/migrations/0001_initial.py +++ b/clatoolkit_project/clatoolkit/migrations/0001_initial.py @@ -180,6 +180,7 @@ class Migration(migrations.Migration): ('blog_id', models.CharField(max_length=255, blank=True)), ('github_account_name', models.CharField(max_length=255, blank=True)), ('trello_account_name', models.CharField(max_length=255, blank=True)), + ('accounts', django_pgjson.fields.JsonBField(null=True)), ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), ], ), diff --git a/clatoolkit_project/clatoolkit/models.py b/clatoolkit_project/clatoolkit/models.py index 65d0c18..fd5ac5a 100644 --- a/clatoolkit_project/clatoolkit/models.py +++ b/clatoolkit_project/clatoolkit/models.py @@ -1,7 +1,7 @@ from django.db import models from django.contrib.auth.models import User -from django_pgjson.fields import JsonField -from django.core.exceptions import ObjectDoesNotExist +from django_pgjson.fields import JsonField, JsonBField +from django.core.exceptions import ObjectDoesNotExist, ValidationError import os class UserProfile(models.Model): @@ -52,6 +52,27 @@ class UserProfile(models.Model): #Trello user ID trello_account_name = models.CharField(max_length=255, blank=True) + # JSON field to store plugin accounts in + accounts = JsonBField(null=True) + + def add_platform_account(self, platform, identifier): + try: + up = self.from_platform_identifier(platform, identifier) + if up != self: + raise ValidationError("This account has already been registered by another user") + + except self.DoesNotExist: + if platform not in self.accounts: + self.accounts[platform] = [] + self.accounts[platform].append(identifier) + + self.save() + + @classmethod + def from_platform_identifier(cls, platform, identifier): + return cls.objects.get(accounts__jcontains={platform: [identifier]}) + + class UserTrelloCourseBoardMap(models.Model): user = models.ForeignKey(User) course_code = models.CharField(max_length=1000, blank=False) diff --git a/clatoolkit_project/dataintegration/core/socialmediarecipebuilder.py b/clatoolkit_project/dataintegration/core/socialmediarecipebuilder.py index c453270..6044091 100644 --- a/clatoolkit_project/dataintegration/core/socialmediarecipebuilder.py +++ b/clatoolkit_project/dataintegration/core/socialmediarecipebuilder.py @@ -204,13 +204,7 @@ def insert_post(user, post_id, message, created_time, unit, platform, platform_u verb = 'created' if check_ifnotinlocallrs(unit, platform, post_id, user, verb): - stm = socialmedia_builder(verb=verb, platform=platform, account_name=get_smid(user, platform), - account_homepage=platform_url, object_type='Note', object_id=post_id, message=message, - timestamp=created_time, account_email=user.email, user_name=user.username, unit=unit, - tags=tags) - jsn = ast.literal_eval(stm.to_json()) - stm_json = pretty_print_json(jsn) - lrs = LearningRecord(xapi=stm_json, unit=unit, verb=verb, platform=platform, user=user, platformid=post_id, + lrs = LearningRecord(xapi=None, unit=unit, verb=verb, platform=platform, user=user, platformid=post_id, message=message, datetimestamp=created_time) lrs.save() for tag in tags: diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py index 110a59f..9bdfc44 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py +++ b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py @@ -5,6 +5,8 @@ from requests_oauthlib import OAuth1, OAuth1Session from dataintegration.models import PlatformConfig from clatoolkit.models import UnitOffering, UnitOfferingMembership +from dataintegration.core.socialmediarecipebuilder import * +from dataintegration.core.recipepermissions import * import requests import json import os @@ -77,13 +79,50 @@ def authorize(self, request): def perform_import(self, retrieval_param, unit): config = unit.platformconfig_set.get(platform=self.platform).config - oauth = OAuth1(client_key=self.client_key, + oauth = OAuth1Session(client_key=self.client_key, client_secret=self.client_secret, resource_owner_key=config["access_token_key"], resource_owner_secret=config["access_token_secret"]) - try: - r = requests.get("{}/wp-json/clatoolkit-wp/v1/posts".format(self.wp_root), auth=oauth) - except Exception as e: - return e - return r.text + next_page = "{}/wp-json/clatoolkit-wp/v1/posts".format(self.wp_root) + + while next_page: + r = oauth.get(next_page) + result = r.json() + + for blog in result["posts"]: + self.add_blog_posts(blog, unit) + + if "next_page" in result: + next_page = result["next_page"] + else: + next_page = False + + def add_blog_posts(self, blog, unit): + + for post in blog["posts"]: + + try: + user = UserProfile.from_platform_identifier(self.platform, post["author"]["email"]).user + + insert_post(user=user, post_id=post["guid"], message=post["post_content"], + created_time=post["post_date_gmt"], unit=unit, platform=self.platform, platform_url="") + + if "comments" in post: + for comment in post["comments"]: + try: + commenter = UserProfile.from_platform_identifier(self.platform, + comment["comment_author_email"]).user + + insert_comment(user=commenter, post_id=post["guid"], comment_id=comment["comment_guid"], + comment_message=comment["comment_content"], + comment_created_time=comment["comment_date_gmt"], unit=unit, + platform=self.platform, platform_url="", parent_user=user) + + except UserProfile.DoesNotExist: + # Don't store the comment if the author doesn't exist + pass + + except UserProfile.DoesNotExist: + # Do nothing if the post author doesn't exist + pass diff --git a/clatoolkit_project/dataintegration/views.py b/clatoolkit_project/dataintegration/views.py index 4219308..5f6901a 100644 --- a/clatoolkit_project/dataintegration/views.py +++ b/clatoolkit_project/dataintegration/views.py @@ -461,4 +461,5 @@ def wp_authorize(request): def wp_refresh(request): unit = UnitOffering.objects.get(id=request.GET["unit"]) - return HttpResponse(WordPressPlugin().perform_import(request, unit)) + WordPressPlugin().perform_import(request, unit) + return HttpResponse("Done") From a342a6bbce31b7df9c43dc56f144be772cb1cb92 Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Fri, 21 Oct 2016 10:36:12 +1100 Subject: [PATCH 04/13] Wordpress plugin declares it's own routes --- .../plugins/wordpress/cladi_plugin.py | 40 ++++++++++++++++--- clatoolkit_project/dataintegration/urls.py | 17 +++++--- clatoolkit_project/dataintegration/views.py | 14 ------- 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py index 9bdfc44..d349bfb 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py +++ b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py @@ -1,16 +1,19 @@ -from django.http import HttpResponseRedirect, Http404 +from django.http import HttpResponse, HttpResponseRedirect, Http404 +from django.conf.urls import patterns, url from django.core.exceptions import PermissionDenied +from django.core.urlresolvers import reverse from common.CLRecipe import CLRecipe from dataintegration.core.plugins.base import DIBasePlugin, DIPluginDashboardMixin -from requests_oauthlib import OAuth1, OAuth1Session +from requests_oauthlib import OAuth1Session from dataintegration.models import PlatformConfig -from clatoolkit.models import UnitOffering, UnitOfferingMembership from dataintegration.core.socialmediarecipebuilder import * from dataintegration.core.recipepermissions import * -import requests -import json +from dataintegration.core.plugins import registry import os +# TODO - make sure user has auth to import +# TODO - comment code + class WordPressPlugin(DIBasePlugin, DIPluginDashboardMixin): platform = CLRecipe.PLATFORM_WORDPRESS @@ -20,6 +23,28 @@ class WordPressPlugin(DIBasePlugin, DIPluginDashboardMixin): user_api_association_name = "WP username" + @staticmethod + def connect_view(request): + return WordPressPlugin().start_authentication(request) + + @staticmethod + def authorize_view(request): + return WordPressPlugin().authorize(request) + + @classmethod + def refresh_view(cls, request): + unit = UnitOffering.objects.get(id=request.GET["unit"]) + cls().perform_import(request, unit) + return HttpResponse("Done") + + @classmethod + def get_url_patterns(cls): + return [ + url(r'^connect/$', cls.connect_view, name="{}-connect".format(CLRecipe.PLATFORM_WORDPRESS)), + url(r'^authorize/$', cls.authorize_view, name="{}-authorize".format(CLRecipe.PLATFORM_WORDPRESS)), + url(r'^refresh/$', cls.refresh_view, name="{}-refresh".format(CLRecipe.PLATFORM_WORDPRESS)), + ] + def __init__(self): self.client_key = os.environ.get("WORDPRESS_KEY") self.client_secret = os.environ.get("WORDPRESS_SECRET") @@ -28,7 +53,8 @@ def __init__(self): def start_authentication(self, request): request_token_url = "{}/oauth1/request".format(self.wp_root) base_authorization_url = "{}/oauth1/authorize".format(self.wp_root) - callback_uri = "{}://{}{}authorize".format(request.scheme, request.get_host(), request.path) + callback_path = reverse("{}-authorize".format(self.platform)) + callback_uri = "{}://{}{}".format(request.scheme, request.get_host(), callback_path) oauth = OAuth1Session(self.client_key, self.client_secret, callback_uri) fetch_response = oauth.fetch_request_token(request_token_url) @@ -126,3 +152,5 @@ def add_blog_posts(self, blog, unit): except UserProfile.DoesNotExist: # Do nothing if the post author doesn't exist pass + +registry.register(WordPressPlugin) diff --git a/clatoolkit_project/dataintegration/urls.py b/clatoolkit_project/dataintegration/urls.py index de09aa5..643d2c1 100755 --- a/clatoolkit_project/dataintegration/urls.py +++ b/clatoolkit_project/dataintegration/urls.py @@ -1,9 +1,17 @@ - -from django.conf.urls import patterns, url +from django.conf import settings +from django.conf.urls import patterns, url, include from dataintegration import views -urlpatterns = patterns( +urlpatterns = [] + +for plugin in settings.DATAINTEGRATION_PLUGINS: + if hasattr(settings.DATAINTEGRATION_PLUGINS[plugin], "get_url_patterns"): + platform = settings.DATAINTEGRATION_PLUGINS[plugin].platform + urlpatterns.append( + url(r'^{}/'.format(platform), include(settings.DATAINTEGRATION_PLUGINS[plugin].get_url_patterns()))) + +urlpatterns += patterns( url(r'^home/$', views.home, name='home'), #url(r'^login/(?P\d+)$', views.login, name='login'), url(r'^get_social/$', views.get_social_media_id, name='get_social'), @@ -23,7 +31,4 @@ url(r'^process_trello/$', views.process_trello, name='processtrello'), url(r'^refreshtrello/$', views.refreshtrello, name='refreshtrello'), #url(r'^ytAuthCallback/(?P\d+)/$', views.ytAuthCallback, name='ytAuthCallback'), - url(r'^wp_connect/authorize$', views.wp_authorize, name='wp_authorize'), - url(r'^wp_connect/$', views.wp_connect, name='wp_connect'), - url(r'^wp_refresh/$', views.wp_refresh, name='wp_refresh'), ) diff --git a/clatoolkit_project/dataintegration/views.py b/clatoolkit_project/dataintegration/views.py index 5f6901a..b57f125 100644 --- a/clatoolkit_project/dataintegration/views.py +++ b/clatoolkit_project/dataintegration/views.py @@ -449,17 +449,3 @@ def assigngroups(request): assign_groups_class(course_code) html_response.write('Groups Assigned') return html_response - - -def wp_connect(request): - return WordPressPlugin().start_authentication(request) - - -def wp_authorize(request): - return WordPressPlugin().authorize(request) - - -def wp_refresh(request): - unit = UnitOffering.objects.get(id=request.GET["unit"]) - WordPressPlugin().perform_import(request, unit) - return HttpResponse("Done") From 409a5109cb241cda94e70e09c2315502e90ae163 Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Fri, 21 Oct 2016 13:50:33 +1100 Subject: [PATCH 05/13] Updated permissions --- .../plugins/wordpress/cladi_plugin.py | 105 +++++++++++------- 1 file changed, 62 insertions(+), 43 deletions(-) diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py index d349bfb..618d114 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py +++ b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py @@ -1,7 +1,8 @@ -from django.http import HttpResponse, HttpResponseRedirect, Http404 +from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponseBadRequest from django.conf.urls import patterns, url from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse +from django.contrib.auth.decorators import login_required from common.CLRecipe import CLRecipe from dataintegration.core.plugins.base import DIBasePlugin, DIPluginDashboardMixin from requests_oauthlib import OAuth1Session @@ -23,26 +24,29 @@ class WordPressPlugin(DIBasePlugin, DIPluginDashboardMixin): user_api_association_name = "WP username" - @staticmethod - def connect_view(request): - return WordPressPlugin().start_authentication(request) + @classmethod + def connect_view(cls, request): + return cls().start_authentication(request) - @staticmethod - def authorize_view(request): - return WordPressPlugin().authorize(request) + @classmethod + def authorize_view(cls, request): + return cls().authorize(request) @classmethod def refresh_view(cls, request): unit = UnitOffering.objects.get(id=request.GET["unit"]) - cls().perform_import(request, unit) - return HttpResponse("Done") + if UnitOfferingMembership.is_admin(request.user, unit): + cls().perform_import(request, unit) + return HttpResponse("Done") + else: + raise PermissionDenied @classmethod def get_url_patterns(cls): return [ - url(r'^connect/$', cls.connect_view, name="{}-connect".format(CLRecipe.PLATFORM_WORDPRESS)), - url(r'^authorize/$', cls.authorize_view, name="{}-authorize".format(CLRecipe.PLATFORM_WORDPRESS)), - url(r'^refresh/$', cls.refresh_view, name="{}-refresh".format(CLRecipe.PLATFORM_WORDPRESS)), + url(r'^connect/$', login_required(cls.connect_view), name="{}-connect".format(CLRecipe.PLATFORM_WORDPRESS)), + url(r'^authorize/$', login_required(cls.authorize_view), name="{}-authorize".format(CLRecipe.PLATFORM_WORDPRESS)), + url(r'^refresh/$', login_required(cls.refresh_view), name="{}-refresh".format(CLRecipe.PLATFORM_WORDPRESS)), ] def __init__(self): @@ -51,43 +55,41 @@ def __init__(self): self.wp_root = os.environ.get("WORDPRESS_ROOT") def start_authentication(self, request): - request_token_url = "{}/oauth1/request".format(self.wp_root) - base_authorization_url = "{}/oauth1/authorize".format(self.wp_root) - callback_path = reverse("{}-authorize".format(self.platform)) - callback_uri = "{}://{}{}".format(request.scheme, request.get_host(), callback_path) - oauth = OAuth1Session(self.client_key, self.client_secret, callback_uri) - fetch_response = oauth.fetch_request_token(request_token_url) + if "unit" not in request.GET: + return HttpResponseBadRequest("Unit not specified") - temp_token = fetch_response.get('oauth_token') - temp_secret = fetch_response.get('oauth_token_secret') + unit_id = request.GET["unit"] - # Save credentials to the session as they will need to be reused in the authorization step - request.session["wp_temp_token"] = temp_token - request.session["wp_temp_secret"] = temp_secret - request.session["wp_temp_unit"] = request.GET["unit"] + try: + unit = UnitOffering.objects.get(id=unit_id) + except UnitOffering.DoesNotExist: + raise Http404 - authorization_url = oauth.authorization_url(base_authorization_url, oauth_callback=callback_uri) + if UnitOfferingMembership.is_admin(request.user, unit): + request_token_url = "{}/oauth1/request".format(self.wp_root) + base_authorization_url = "{}/oauth1/authorize".format(self.wp_root) + callback_path = reverse("{}-authorize".format(self.platform)) + callback_uri = "{}://{}{}".format(request.scheme, request.get_host(), callback_path) - return HttpResponseRedirect(authorization_url) + oauth = OAuth1Session(self.client_key, self.client_secret, callback_uri) + fetch_response = oauth.fetch_request_token(request_token_url) - def authorize(self, request): - access_token_url = "{}/oauth1/access".format(self.wp_root) + temp_token = fetch_response.get('oauth_token') + temp_secret = fetch_response.get('oauth_token_secret') - verifier = request.GET['oauth_verifier'] + # Save credentials to the session as they will need to be reused in the authorization step + request.session["wp_temp_token"] = temp_token + request.session["wp_temp_secret"] = temp_secret + request.session["wp_temp_unit"] = request.GET["unit"] - oauth = OAuth1Session(self.client_key, - client_secret=self.client_secret, - resource_owner_key=request.session["wp_temp_token"], - resource_owner_secret=request.session["wp_temp_secret"], - verifier=verifier) - oauth_tokens = oauth.fetch_access_token(access_token_url) + authorization_url = oauth.authorization_url(base_authorization_url, oauth_callback=callback_uri) - config_dict = { - "access_token_key": oauth_tokens.get('oauth_token'), - "access_token_secret": oauth_tokens.get('oauth_token_secret') - } + return HttpResponseRedirect(authorization_url) + else: + raise PermissionDenied + def authorize(self, request): unit_id = request.session["wp_temp_unit"] try: @@ -96,13 +98,30 @@ def authorize(self, request): raise Http404 if UnitOfferingMembership.is_admin(request.user, unit): - config = PlatformConfig(unit=unit, config=config_dict, platform=self.platform) - config.save() + access_token_url = "{}/oauth1/access".format(self.wp_root) + + verifier = request.GET['oauth_verifier'] + + oauth = OAuth1Session(self.client_key, + client_secret=self.client_secret, + resource_owner_key=request.session["wp_temp_token"], + resource_owner_secret=request.session["wp_temp_secret"], + verifier=verifier) + oauth_tokens = oauth.fetch_access_token(access_token_url) + + config_dict = { + "access_token_key": oauth_tokens.get('oauth_token'), + "access_token_secret": oauth_tokens.get('oauth_token_secret') + } + + pc = PlatformConfig(unit=unit, platform=self.platform, config=config_dict) + pc.save() + + return HttpResponseRedirect("/") + else: raise PermissionDenied - return HttpResponseRedirect("/") - def perform_import(self, retrieval_param, unit): config = unit.platformconfig_set.get(platform=self.platform).config oauth = OAuth1Session(client_key=self.client_key, From e1f80b9d05327008e7e0ab778af8f868c21a7ef3 Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Fri, 21 Oct 2016 16:52:25 +1100 Subject: [PATCH 06/13] Admins can connect a WP instance and configure it from a rough but working form. Support for multiple wordpress intances. --- .env.example | 5 - .../clatoolkit_project/settings.py | 32 +-- .../plugins/wordpress/cladi_plugin.py | 206 +++++++++++------- .../plugins/wordpress/forms.py | 8 + .../plugins/wordpress/templates/connect.html | 14 ++ 5 files changed, 169 insertions(+), 96 deletions(-) create mode 100644 clatoolkit_project/dataintegration/plugins/wordpress/forms.py create mode 100644 clatoolkit_project/dataintegration/plugins/wordpress/templates/connect.html diff --git a/.env.example b/.env.example index ef3fbdf..9f6a944 100644 --- a/.env.example +++ b/.env.example @@ -48,11 +48,6 @@ TWITTER_APP_SECRET= TWITTER_OAUTH_TOKEN= TWITTER_OAUTH_TOKEN_SECRET= -# WordPress -WORDPRESS_ROOT= -WORDPRESS_KEY= -WORDPRESS_SECRET= - # YouTube YOUTUBE_CLIENT_ID= YOUTUBE_CLIENT_SECRET= \ No newline at end of file diff --git a/clatoolkit_project/clatoolkit_project/settings.py b/clatoolkit_project/clatoolkit_project/settings.py index 1b52a36..d41f120 100644 --- a/clatoolkit_project/clatoolkit_project/settings.py +++ b/clatoolkit_project/clatoolkit_project/settings.py @@ -81,22 +81,6 @@ ROOT_URLCONF = 'clatoolkit_project.urls' -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'templates')], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - WSGI_APPLICATION = 'clatoolkit_project.wsgi.application' @@ -200,3 +184,19 @@ DATAINTEGRATION_PLUGINS_INCLUDEDASHBOARD_PLATFORMS = get_includeindashboardwidgets_platforms() DATAINTEGRATION_PLUGINS = get_plugins() DATAINTEGRATION_PLUGINS_INCLUDEAUTHOMATIC = get_includeauthomaticplugins_platforms() + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates'), PLUGIN_PATH], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py index 618d114..f8e6409 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py +++ b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py @@ -1,5 +1,6 @@ -from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponseBadRequest -from django.conf.urls import patterns, url +from django.http import HttpResponse, HttpResponseRedirect, Http404 +from django.shortcuts import render +from django.conf.urls import url from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.contrib.auth.decorators import login_required @@ -10,10 +11,7 @@ from dataintegration.core.socialmediarecipebuilder import * from dataintegration.core.recipepermissions import * from dataintegration.core.plugins import registry -import os - -# TODO - make sure user has auth to import -# TODO - comment code +from forms import ConnectForm class WordPressPlugin(DIBasePlugin, DIPluginDashboardMixin): @@ -26,7 +24,49 @@ class WordPressPlugin(DIBasePlugin, DIPluginDashboardMixin): @classmethod def connect_view(cls, request): - return cls().start_authentication(request) + if request.method == "POST": + form = ConnectForm(request.POST) + + if form.is_valid(): + # Get the details the user entered into the form + unit_id = form.cleaned_data["unit_id"] + wp_root = form.cleaned_data["wp_root"] + client_key = form.cleaned_data["client_key"] + client_secret = form.cleaned_data["client_secret"] + + unit = UnitOffering.objects.get(id=unit_id) + + if UnitOfferingMembership.is_admin(request.user, unit): + # Save the WP instance details + cls.add_instance(unit, wp_root, client_key, client_secret) + + # Setup URLs for use in the handshake process + request_token_url = "{}/oauth1/request".format(wp_root) + base_authorization_url = "{}/oauth1/authorize".format(wp_root) + callback_path = reverse("{}-authorize".format(cls.platform)) + callback_uri = "{}://{}{}".format(request.scheme, request.get_host(), callback_path) + + # Start the oauth flow and get temporary credentials + oauth = OAuth1Session(client_key, client_secret, callback_uri) + fetch_response = oauth.fetch_request_token(request_token_url) + temp_token = fetch_response.get('oauth_token') + temp_secret = fetch_response.get('oauth_token_secret') + + # Save credentials to the session as they will need to be reused in the authorization step + request.session["wp_temp_token"] = temp_token + request.session["wp_temp_secret"] = temp_secret + request.session["wp_temp_unit"] = request.GET["unit"] + request.session["wp_temp_root"] = wp_root + + # Redirect the user to authorize the toolkit + authorization_url = oauth.authorization_url(base_authorization_url, oauth_callback=callback_uri) + return HttpResponseRedirect(authorization_url) + + else: + unit_id = request.GET["unit"] + form = ConnectForm() + + return render(request, "wordpress/templates/connect.html", {"form": form, "unit_id":unit_id}) @classmethod def authorize_view(cls, request): @@ -43,6 +83,7 @@ def refresh_view(cls, request): @classmethod def get_url_patterns(cls): + """Returns the URL patterns used by the plugin""" return [ url(r'^connect/$', login_required(cls.connect_view), name="{}-connect".format(CLRecipe.PLATFORM_WORDPRESS)), url(r'^authorize/$', login_required(cls.authorize_view), name="{}-authorize".format(CLRecipe.PLATFORM_WORDPRESS)), @@ -50,16 +91,12 @@ def get_url_patterns(cls): ] def __init__(self): - self.client_key = os.environ.get("WORDPRESS_KEY") - self.client_secret = os.environ.get("WORDPRESS_SECRET") - self.wp_root = os.environ.get("WORDPRESS_ROOT") - - def start_authentication(self, request): + pass - if "unit" not in request.GET: - return HttpResponseBadRequest("Unit not specified") - - unit_id = request.GET["unit"] + @classmethod + def authorize(cls, request): + unit_id = request.session["wp_temp_unit"] + wp_root = request.session["wp_temp_root"] try: unit = UnitOffering.objects.get(id=unit_id) @@ -67,102 +104,121 @@ def start_authentication(self, request): raise Http404 if UnitOfferingMembership.is_admin(request.user, unit): - request_token_url = "{}/oauth1/request".format(self.wp_root) - base_authorization_url = "{}/oauth1/authorize".format(self.wp_root) - callback_path = reverse("{}-authorize".format(self.platform)) - callback_uri = "{}://{}{}".format(request.scheme, request.get_host(), callback_path) + access_token_url = "{}/oauth1/access".format(wp_root) + + verifier = request.GET['oauth_verifier'] - oauth = OAuth1Session(self.client_key, self.client_secret, callback_uri) - fetch_response = oauth.fetch_request_token(request_token_url) + client_key, client_secret = cls.get_client_key(unit, wp_root) - temp_token = fetch_response.get('oauth_token') - temp_secret = fetch_response.get('oauth_token_secret') + oauth = OAuth1Session(client_key, client_secret=client_secret, + resource_owner_key=request.session["wp_temp_token"], + resource_owner_secret=request.session["wp_temp_secret"], verifier=verifier) + oauth_tokens = oauth.fetch_access_token(access_token_url) - # Save credentials to the session as they will need to be reused in the authorization step - request.session["wp_temp_token"] = temp_token - request.session["wp_temp_secret"] = temp_secret - request.session["wp_temp_unit"] = request.GET["unit"] + cls.save_access_token(unit, wp_root, oauth_tokens.get('oauth_token'), oauth_tokens.get('oauth_token_secret')) - authorization_url = oauth.authorization_url(base_authorization_url, oauth_callback=callback_uri) + return HttpResponseRedirect("/") - return HttpResponseRedirect(authorization_url) else: raise PermissionDenied - def authorize(self, request): - unit_id = request.session["wp_temp_unit"] - + @classmethod + def add_instance(cls, unit, wp_root, access_key, access_secret): + """Add a connection to the platform config for a unit, overwrites existing entries for a non-unique wp_root""" try: - unit = UnitOffering.objects.get(id=unit_id) - except UnitOffering.DoesNotExist: - raise Http404 + pc = PlatformConfig.objects.get(unit=unit, platform=cls.platform) + pc.config[wp_root] = { + "client_key": access_key, + "client_secret": access_secret + } + # If no existing config exists + except PlatformConfig.DoesNotExist: + config = { + wp_root: { + "client_key": access_key, + "client_secret": access_secret + } + } + pc = PlatformConfig(unit=unit, platform=cls.platform, config=config) + pc.save() - if UnitOfferingMembership.is_admin(request.user, unit): - access_token_url = "{}/oauth1/access".format(self.wp_root) + @classmethod + def get_platform_config(cls, unit): + return PlatformConfig.objects.get(unit=unit, platform=cls.platform).config - verifier = request.GET['oauth_verifier'] + @classmethod + def get_instance_config(cls, unit, wp_root): + return PlatformConfig.objects.get(unit=unit, platform=cls.platform).config[wp_root] - oauth = OAuth1Session(self.client_key, - client_secret=self.client_secret, - resource_owner_key=request.session["wp_temp_token"], - resource_owner_secret=request.session["wp_temp_secret"], - verifier=verifier) - oauth_tokens = oauth.fetch_access_token(access_token_url) + @classmethod + def get_client_key(cls, unit, wp_root): + config = cls.get_instance_config(unit, wp_root) - config_dict = { - "access_token_key": oauth_tokens.get('oauth_token'), - "access_token_secret": oauth_tokens.get('oauth_token_secret') - } + return config["client_key"], config["client_secret"] + + @classmethod + def save_access_token(cls, unit, wp_root, access_token_key, access_token_secret): + """Save access token and secret to an existing entry in the PlatformConfig""" + pc = PlatformConfig.objects.get(unit=unit, platform=cls.platform) - pc = PlatformConfig(unit=unit, platform=self.platform, config=config_dict) - pc.save() + pc.config[wp_root]["access_token_key"] = access_token_key + pc.config[wp_root]["access_token_secret"] = access_token_secret - return HttpResponseRedirect("/") + pc.save() - else: - raise PermissionDenied + @classmethod + def get_access_token(cls, unit, wp_root): + config = cls.get_instance_config(unit, wp_root) - def perform_import(self, retrieval_param, unit): - config = unit.platformconfig_set.get(platform=self.platform).config - oauth = OAuth1Session(client_key=self.client_key, - client_secret=self.client_secret, - resource_owner_key=config["access_token_key"], - resource_owner_secret=config["access_token_secret"]) + return config["access_token_key"], config["access_token_secret"] - next_page = "{}/wp-json/clatoolkit-wp/v1/posts".format(self.wp_root) + @classmethod + def perform_import(cls, retrieval_param, unit): + config = cls.get_platform_config(unit) + + # For each WordPress instance connected + for instance in config: + + oauth = OAuth1Session(client_key=config[instance]["client_key"], + client_secret=config[instance]["client_secret"], + resource_owner_key=config[instance]["access_token_key"], + resource_owner_secret=config[instance]["access_token_secret"]) - while next_page: - r = oauth.get(next_page) - result = r.json() + next_page = "{}/wp-json/clatoolkit-wp/v1/posts".format(instance) - for blog in result["posts"]: - self.add_blog_posts(blog, unit) + while next_page: + r = oauth.get(next_page) + result = r.json() - if "next_page" in result: - next_page = result["next_page"] - else: - next_page = False + for blog in result["posts"]: + cls.add_blog_posts(blog, unit) - def add_blog_posts(self, blog, unit): + if "next_page" in result: + next_page = result["next_page"] + else: + next_page = False + + @classmethod + def add_blog_posts(cls, blog, unit): for post in blog["posts"]: try: - user = UserProfile.from_platform_identifier(self.platform, post["author"]["email"]).user + user = UserProfile.from_platform_identifier(cls.platform, post["author"]["email"]).user insert_post(user=user, post_id=post["guid"], message=post["post_content"], - created_time=post["post_date_gmt"], unit=unit, platform=self.platform, platform_url="") + created_time=post["post_date_gmt"], unit=unit, platform=cls.platform, platform_url="") if "comments" in post: for comment in post["comments"]: try: - commenter = UserProfile.from_platform_identifier(self.platform, + commenter = UserProfile.from_platform_identifier(cls.platform, comment["comment_author_email"]).user insert_comment(user=commenter, post_id=post["guid"], comment_id=comment["comment_guid"], comment_message=comment["comment_content"], comment_created_time=comment["comment_date_gmt"], unit=unit, - platform=self.platform, platform_url="", parent_user=user) + platform=cls.platform, platform_url="", parent_user=user) except UserProfile.DoesNotExist: # Don't store the comment if the author doesn't exist diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/forms.py b/clatoolkit_project/dataintegration/plugins/wordpress/forms.py new file mode 100644 index 0000000..a36828f --- /dev/null +++ b/clatoolkit_project/dataintegration/plugins/wordpress/forms.py @@ -0,0 +1,8 @@ +from django import forms + + +class ConnectForm(forms.Form): + wp_root = forms.URLField(label="WordPress Root URL") + client_key = forms.CharField() + client_secret = forms.CharField() + unit_id = forms.CharField(widget=forms.HiddenInput()) diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/templates/connect.html b/clatoolkit_project/dataintegration/plugins/wordpress/templates/connect.html new file mode 100644 index 0000000..25ba03d --- /dev/null +++ b/clatoolkit_project/dataintegration/plugins/wordpress/templates/connect.html @@ -0,0 +1,14 @@ +
+ {% csrf_token %} + {{ form.wp_root.label_tag }} + {{ form.wp_root }} +
+ {{ form.client_key.label_tag }} + {{ form.client_key }} +
+ {{ form.client_secret.label_tag }} + {{ form.client_secret }} +
+ + +
\ No newline at end of file From 17b26f6151fbf545cbd3af1b5d3e4ed4c925e511 Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Mon, 24 Oct 2016 16:58:03 +1100 Subject: [PATCH 07/13] WIP but non functional importing of friendships from BuddyPress --- .../clatoolkit/migrations/0001_initial.py | 7 ++- clatoolkit_project/clatoolkit/models.py | 15 ++++- .../core/socialmediarecipebuilder.py | 33 ++++++++++- .../plugins/wordpress/cladi_plugin.py | 58 +++++++++++++++---- 4 files changed, 95 insertions(+), 18 deletions(-) diff --git a/clatoolkit_project/clatoolkit/migrations/0001_initial.py b/clatoolkit_project/clatoolkit/migrations/0001_initial.py index 23868ef..f374fa8 100644 --- a/clatoolkit_project/clatoolkit/migrations/0001_initial.py +++ b/clatoolkit_project/clatoolkit/migrations/0001_initial.py @@ -74,6 +74,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('xapi', django_pgjson.fields.JsonField()), ('platform', models.CharField(max_length=5000)), + ('platform_group_id', models.CharField(max_length=100, blank=True)), ('verb', models.CharField(max_length=5000)), ('platformid', models.CharField(max_length=5000, blank=True)), ('platformparentid', models.CharField(max_length=5000, blank=True)), @@ -108,10 +109,12 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('platform', models.CharField(max_length=5000)), - ('verb', models.CharField(max_length=5000)), + ('platform_group_id', models.CharField(max_length=100, blank=True)), + ('type', models.CharField(max_length=100, blank=True)), + ('verb', models.CharField(max_length=5000, blank=True)), ('to_external_user', models.CharField(max_length=5000, null=True, blank=True)), ('platformid', models.CharField(max_length=5000, blank=True)), - ('message', models.TextField()), + ('message', models.TextField(blank=True)), ('datetimestamp', models.DateTimeField(blank=True)), ('from_user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), ('to_user', models.ForeignKey(related_name='to_user', to=settings.AUTH_USER_MODEL, null=True)), diff --git a/clatoolkit_project/clatoolkit/models.py b/clatoolkit_project/clatoolkit/models.py index fd5ac5a..fce1b75 100644 --- a/clatoolkit_project/clatoolkit/models.py +++ b/clatoolkit_project/clatoolkit/models.py @@ -238,6 +238,7 @@ class LearningRecord(models.Model): xapi = JsonField() unit = models.ForeignKey(UnitOffering) platform = models.CharField(max_length=5000, blank=False) + platform_group_id = models.CharField(max_length=100, blank=True) verb = models.CharField(max_length=5000, blank=False) user = models.ForeignKey(User) platformid = models.CharField(max_length=5000, blank=True) @@ -253,14 +254,24 @@ class LearningRecord(models.Model): class SocialRelationship(models.Model): unit = models.ForeignKey(UnitOffering) platform = models.CharField(max_length=5000, blank=False) - verb = models.CharField(max_length=5000, blank=False) + platform_group_id = models.CharField(max_length=100, blank=True) + type = models.CharField(max_length=100, blank=True) + verb = models.CharField(max_length=5000, blank=True) from_user = models.ForeignKey(User) to_user = models.ForeignKey(User, null=True, related_name="to_user") to_external_user = models.CharField(max_length=5000, blank=True, null=True) platformid = models.CharField(max_length=5000, blank=True) - message = models.TextField(blank=False) + message = models.TextField(blank=True) datetimestamp = models.DateTimeField(blank=True) + @classmethod + def relationship_exists(cls, **kwargs): + try: + cls.objects.get(kwargs) + return True + except cls.DoesNotExist: + return False + class CachedContent(models.Model): htmltable = models.TextField(blank=False) diff --git a/clatoolkit_project/dataintegration/core/socialmediarecipebuilder.py b/clatoolkit_project/dataintegration/core/socialmediarecipebuilder.py index 6044091..9102bff 100644 --- a/clatoolkit_project/dataintegration/core/socialmediarecipebuilder.py +++ b/clatoolkit_project/dataintegration/core/socialmediarecipebuilder.py @@ -200,12 +200,12 @@ def socialmedia_builder(verb, platform, account_name, account_homepage, object_t return statement -def insert_post(user, post_id, message, created_time, unit, platform, platform_url, tags=()): +def insert_post(user, post_id, message, created_time, unit, platform, platform_url, platform_group_id=None, tags=()): verb = 'created' if check_ifnotinlocallrs(unit, platform, post_id, user, verb): lrs = LearningRecord(xapi=None, unit=unit, verb=verb, platform=platform, user=user, platformid=post_id, - message=message, datetimestamp=created_time) + message=message, datetimestamp=created_time, platform_group_id=platform_group_id) lrs.save() for tag in tags: if tag[0] == "@": @@ -420,6 +420,35 @@ def insert_closedopen_object(usr_dict, object_id, object_text, obj_updater_uid, lrs.save() +def insert_relationship(from_user, to_user, relationship_type, unit, platform, platform_group_id=None, + directional=True): + search_args = { + "from_user": from_user, + "to_user": to_user, + "type": relationship_type, + "unit": unit, + "platform": platform + } + + if platform_group_id: + search_args["platform_group_id"] = platform_group_id + + # Check if it exists + exists = True if SocialRelationship.relationship_exists(**search_args) else False + + # If the relationship is not direction check if the opposite exists + if not directional: + search_args["from_user"] = to_user + search_args["to_user"] = from_user + + exists = True if SocialRelationship.relationship_exists(**search_args) else False + + # Create the relationship if it doesn't already exist + if not exists: + sr = SocialRelationship(unit=unit, platform=platform, platform_group_id=platform_group_id, + type=relationship_type, from_user=from_user, to_user=to_user, ) + sr.save() + """def insert_comment(usr_dict, post_id, comment_id, comment_message, comment_from_uid, comment_from_name, comment_created_time, course_code, platform, platform_url, shared_username=None, shared_displayname=None): diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py index f8e6409..433d7e3 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py +++ b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py @@ -184,22 +184,27 @@ def perform_import(cls, retrieval_param, unit): resource_owner_key=config[instance]["access_token_key"], resource_owner_secret=config[instance]["access_token_secret"]) - next_page = "{}/wp-json/clatoolkit-wp/v1/posts".format(instance) + cls.get_posts(unit, oauth, instance) + cls.get_friendships(unit, oauth, instance) - while next_page: - r = oauth.get(next_page) - result = r.json() + @classmethod + def get_posts(cls, unit, oauth, instance): + next_page = "{}/wp-json/clatoolkit-wp/v1/posts".format(instance) + + while next_page: + r = oauth.get(next_page) + result = r.json() - for blog in result["posts"]: - cls.add_blog_posts(blog, unit) + for blog in result["posts"]: + cls.add_blog_posts(blog, unit, instance) - if "next_page" in result: - next_page = result["next_page"] - else: - next_page = False + if "next_page" in result: + next_page = result["next_page"] + else: + next_page = False @classmethod - def add_blog_posts(cls, blog, unit): + def add_blog_posts(cls, blog, unit, instance): for post in blog["posts"]: @@ -207,7 +212,8 @@ def add_blog_posts(cls, blog, unit): user = UserProfile.from_platform_identifier(cls.platform, post["author"]["email"]).user insert_post(user=user, post_id=post["guid"], message=post["post_content"], - created_time=post["post_date_gmt"], unit=unit, platform=cls.platform, platform_url="") + created_time=post["post_date_gmt"], unit=unit, platform=cls.platform, platform_url="", + platform_group_id=instance) if "comments" in post: for comment in post["comments"]: @@ -228,4 +234,32 @@ def add_blog_posts(cls, blog, unit): # Do nothing if the post author doesn't exist pass + @classmethod + def get_friendships(cls, unit, oauth, instance): + friendship_endpoint = "{}/wp-json/clatoolkit-wp/v1/friendships".format(instance) + relationship_type = "friendship" + + r = oauth.get(friendship_endpoint) + + j = r.json() + + for user_id in j["friendships"]: + user_email = j["entities"][user_id]["email"] + try: + user = UserProfile.from_platform_identifier(platform=cls.platform, identifier=user_email) + for friend_id in j["friendships"][user_id]: + friend_user_email = j["entities"][friend_id]["email"] + + try: + friend = UserProfile.from_platform_identifier(cls.platform, friend_user_email) + + insert_relationship(from_user=user, to_user=friend, relationship_type=relationship_type, + unit=unit, platform=cls.platform, platform_group_id=instance, + directional=False) + + except UserProfile.DoesNotExist: + pass + except UserProfile.DoesNotExist: + pass + registry.register(WordPressPlugin) From a6fd6fc1e3a5b998d9783c4dd2be27bcd13aebb9 Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Tue, 25 Oct 2016 09:40:09 +1100 Subject: [PATCH 08/13] Importing freindships from BuddyPress working --- clatoolkit_project/clatoolkit/migrations/0001_initial.py | 2 +- clatoolkit_project/clatoolkit/models.py | 4 ++-- .../dataintegration/plugins/wordpress/cladi_plugin.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clatoolkit_project/clatoolkit/migrations/0001_initial.py b/clatoolkit_project/clatoolkit/migrations/0001_initial.py index f374fa8..b5be2f9 100644 --- a/clatoolkit_project/clatoolkit/migrations/0001_initial.py +++ b/clatoolkit_project/clatoolkit/migrations/0001_initial.py @@ -115,7 +115,7 @@ class Migration(migrations.Migration): ('to_external_user', models.CharField(max_length=5000, null=True, blank=True)), ('platformid', models.CharField(max_length=5000, blank=True)), ('message', models.TextField(blank=True)), - ('datetimestamp', models.DateTimeField(blank=True)), + ('datetimestamp', models.DateTimeField(null=True)), ('from_user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), ('to_user', models.ForeignKey(related_name='to_user', to=settings.AUTH_USER_MODEL, null=True)), ], diff --git a/clatoolkit_project/clatoolkit/models.py b/clatoolkit_project/clatoolkit/models.py index fce1b75..20efffc 100644 --- a/clatoolkit_project/clatoolkit/models.py +++ b/clatoolkit_project/clatoolkit/models.py @@ -262,12 +262,12 @@ class SocialRelationship(models.Model): to_external_user = models.CharField(max_length=5000, blank=True, null=True) platformid = models.CharField(max_length=5000, blank=True) message = models.TextField(blank=True) - datetimestamp = models.DateTimeField(blank=True) + datetimestamp = models.DateTimeField(null=True) @classmethod def relationship_exists(cls, **kwargs): try: - cls.objects.get(kwargs) + cls.objects.get(**kwargs) return True except cls.DoesNotExist: return False diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py index 433d7e3..019b80d 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py +++ b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py @@ -246,12 +246,12 @@ def get_friendships(cls, unit, oauth, instance): for user_id in j["friendships"]: user_email = j["entities"][user_id]["email"] try: - user = UserProfile.from_platform_identifier(platform=cls.platform, identifier=user_email) + user = UserProfile.from_platform_identifier(platform=cls.platform, identifier=user_email).user for friend_id in j["friendships"][user_id]: friend_user_email = j["entities"][friend_id]["email"] try: - friend = UserProfile.from_platform_identifier(cls.platform, friend_user_email) + friend = UserProfile.from_platform_identifier(cls.platform, friend_user_email).user insert_relationship(from_user=user, to_user=friend, relationship_type=relationship_type, unit=unit, platform=cls.platform, platform_group_id=instance, From fcd7333da9bd0a6ba51f6e6240b9063550a73f09 Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Tue, 25 Oct 2016 09:48:26 +1100 Subject: [PATCH 09/13] Add platform_group_id to more recipes --- clatoolkit_project/clatoolkit/models.py | 2 +- .../core/socialmediarecipebuilder.py | 43 ++++++++++--------- .../plugins/wordpress/cladi_plugin.py | 2 +- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/clatoolkit_project/clatoolkit/models.py b/clatoolkit_project/clatoolkit/models.py index 20efffc..851d473 100644 --- a/clatoolkit_project/clatoolkit/models.py +++ b/clatoolkit_project/clatoolkit/models.py @@ -254,7 +254,7 @@ class LearningRecord(models.Model): class SocialRelationship(models.Model): unit = models.ForeignKey(UnitOffering) platform = models.CharField(max_length=5000, blank=False) - platform_group_id = models.CharField(max_length=100, blank=True) + platform_group_id = models.CharField(max_length=255, blank=True) type = models.CharField(max_length=100, blank=True) verb = models.CharField(max_length=5000, blank=True) from_user = models.ForeignKey(User) diff --git a/clatoolkit_project/dataintegration/core/socialmediarecipebuilder.py b/clatoolkit_project/dataintegration/core/socialmediarecipebuilder.py index 9102bff..cfa3e00 100644 --- a/clatoolkit_project/dataintegration/core/socialmediarecipebuilder.py +++ b/clatoolkit_project/dataintegration/core/socialmediarecipebuilder.py @@ -237,19 +237,22 @@ def insert_blogpost(usr_dict, post_id,message,from_name,from_uid, created_time, socialrelationship.save() -def insert_like(user, post_id, message, unit, platform, created_time=None, parent_user=None, parent_user_external=None): +def insert_like(user, post_id, message, unit, platform, platform_group_id=None, created_time=None, parent_user=None, + parent_user_external=None): verb = "liked" if check_ifnotinlocallrs(unit, platform, post_id, user, verb): - lrs = LearningRecord(xapi=None, unit=unit, verb=verb, platform=platform, user=user, platformid=post_id, - message=message, platformparentid=post_id, parent_user=parent_user, - parent_user_external=parent_user_external, datetimestamp=created_time) + lrs = LearningRecord(xapi=None, unit=unit, verb=verb, platform=platform, platform_group_id=platform_group_id, + user=user, platformid=post_id, message=message, platformparentid=post_id, + parent_user=parent_user, parent_user_external=parent_user_external, + datetimestamp=created_time) lrs.save() sr = SocialRelationship(unit=unit, verb=verb, from_user=user, to_user=parent_user, - to_external_user=parent_user_external, platform=platform, message=message, - datetimestamp=created_time, platformid=post_id) + to_external_user=parent_user_external, platform=platform, + platform_group_id=platform_group_id, message=message, datetimestamp=created_time, + platformid=post_id) sr.save() @@ -270,37 +273,35 @@ def insert_blogcomment(usr_dict, post_id, comment_id, comment_message, comment_f def insert_comment(user, post_id, comment_id, comment_message, comment_created_time, unit, platform, platform_url, - parent_user=None, parent_user_external=None): - + platform_group_id=None, parent_user=None, parent_user_external=None): if check_ifnotinlocallrs(unit, platform, comment_id): lrs = LearningRecord(xapi=None, unit=unit, verb='commented', platform=platform, user=user, - platformid=comment_id, platformparentid=post_id, parent_user=parent_user, - parent_user_external=parent_user_external, message=comment_message, - datetimestamp=comment_created_time) + platformid=comment_id, platformparentid=post_id, platform_group_id=platform_group_id, + parent_user=parent_user, parent_user_external=parent_user_external, + message=comment_message, datetimestamp=comment_created_time) lrs.save() sr = SocialRelationship(verb="commented", from_user=user, to_user=parent_user, - to_external_user=parent_user_external, platform=platform, message=comment_message, + to_external_user=parent_user_external, platform=platform, + platform_group_id=platform_group_id, message=comment_message, datetimestamp=comment_created_time, unit=unit, platformid=comment_id) sr.save() -def insert_share(user, post_id, share_id, comment_message, comment_created_time, unit, platform, platform_url, tags=(), - parent_user=None, parent_external_user=None): +def insert_share(user, post_id, share_id, comment_message, comment_created_time, unit, platform, platform_url, + platform_group_id=None, tags=(), parent_user=None, parent_external_user=None): if check_ifnotinlocallrs(unit, platform, share_id): - # TODO - re-enable xAPI - # stm = socialmedia_builder(verb='shared', platform=platform, account_name=comment_from_uid, account_homepage=platform_url, object_type='Note', object_id=share_id, message=comment_message, parent_id=post_id, parent_object_type='Note', timestamp=comment_created_time, account_email=usr_dict['email'], user_name=comment_from_name, course_code=course_code, tags=tags ) - # jsn = ast.literal_eval(stm.to_json()) - # stm_json = pretty_print_json(jsn) - lrs = LearningRecord(xapi="{}", unit=unit, verb='shared', platform=platform, user=user, platformid=share_id, + lrs = LearningRecord(xapi=None, unit=unit, verb='shared', platform=platform, + platform_group_id=platform_group_id, user=user, platformid=share_id, platformparentid=post_id, parent_user=parent_user, parent_external_user=parent_external_user, message=comment_message, datetimestamp=comment_created_time) lrs.save() sr = SocialRelationship(verb="shared", from_user=user, to_user=parent_user, - to_external_user=parent_external_user, platform=platform, message=comment_message, + to_external_user=parent_external_user, platform=platform, + platform_group_id=platform_group_id, message=comment_message, datetimestamp=comment_created_time, course_code=unit.code, platformid=share_id) sr.save() @@ -446,7 +447,7 @@ def insert_relationship(from_user, to_user, relationship_type, unit, platform, p # Create the relationship if it doesn't already exist if not exists: sr = SocialRelationship(unit=unit, platform=platform, platform_group_id=platform_group_id, - type=relationship_type, from_user=from_user, to_user=to_user, ) + type=relationship_type, from_user=from_user, to_user=to_user) sr.save() diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py index 019b80d..fe41f7f 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py +++ b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py @@ -224,7 +224,7 @@ def add_blog_posts(cls, blog, unit, instance): insert_comment(user=commenter, post_id=post["guid"], comment_id=comment["comment_guid"], comment_message=comment["comment_content"], comment_created_time=comment["comment_date_gmt"], unit=unit, - platform=cls.platform, platform_url="", parent_user=user) + platform=cls.platform, platform_group_id=instance, platform_url="", parent_user=user) except UserProfile.DoesNotExist: # Don't store the comment if the author doesn't exist From e04740339673dc79c52865606f1e8eebf2da71d7 Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Tue, 25 Oct 2016 10:33:09 +1100 Subject: [PATCH 10/13] Fixed bug where LearningRecord datetime would always be overridden with the current time --- clatoolkit_project/clatoolkit/migrations/0001_initial.py | 3 ++- clatoolkit_project/clatoolkit/models.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/clatoolkit_project/clatoolkit/migrations/0001_initial.py b/clatoolkit_project/clatoolkit/migrations/0001_initial.py index e2aa03a..861d212 100644 --- a/clatoolkit_project/clatoolkit/migrations/0001_initial.py +++ b/clatoolkit_project/clatoolkit/migrations/0001_initial.py @@ -3,6 +3,7 @@ from django.db import models, migrations import django_pgjson.fields +import django.utils.timezone from django.conf import settings @@ -79,7 +80,7 @@ class Migration(migrations.Migration): ('platformparentid', models.CharField(max_length=5000, blank=True)), ('parent_user_external', models.CharField(max_length=5000, null=True, blank=True)), ('message', models.TextField(blank=True)), - ('datetimestamp', models.DateTimeField(auto_now_add=True, null=True)), + ('datetimestamp', models.DateTimeField(default=django.utils.timezone.now)), ('senttolrs', models.CharField(max_length=5000, blank=True)), ('parent_user', models.ForeignKey(related_name='parent_user', to=settings.AUTH_USER_MODEL, null=True)), ], diff --git a/clatoolkit_project/clatoolkit/models.py b/clatoolkit_project/clatoolkit/models.py index 65d0c18..1182d7e 100644 --- a/clatoolkit_project/clatoolkit/models.py +++ b/clatoolkit_project/clatoolkit/models.py @@ -2,6 +2,7 @@ from django.contrib.auth.models import User from django_pgjson.fields import JsonField from django.core.exceptions import ObjectDoesNotExist +from django.utils import timezone import os class UserProfile(models.Model): @@ -225,7 +226,7 @@ class LearningRecord(models.Model): parent_user = models.ForeignKey(User, null=True, related_name="parent_user") parent_user_external = models.CharField(max_length=5000, blank=True, null=True) message = models.TextField(blank=True) - datetimestamp = models.DateTimeField(auto_now_add=True, null=True) + datetimestamp = models.DateTimeField(default=timezone.now) senttolrs = models.CharField(max_length=5000, blank=True) From 3a9ff19d28c9727c49af2a832bc630fa569292b0 Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Tue, 25 Oct 2016 12:56:36 +1100 Subject: [PATCH 11/13] WordPress plugin config page + updated connect page --- clatoolkit_project/clatoolkit/models.py | 15 ++++- .../clatoolkit_project/settings.py | 55 +++++++++-------- .../plugins/wordpress/cladi_plugin.py | 57 ++++++++++++++--- .../plugins/wordpress/forms.py | 6 +- .../plugins/wordpress/static/css/default.css | 12 ++++ .../plugins/wordpress/templates/config.html | 43 +++++++++++++ .../plugins/wordpress/templates/connect.html | 61 ++++++++++++++----- 7 files changed, 198 insertions(+), 51 deletions(-) create mode 100644 clatoolkit_project/dataintegration/plugins/wordpress/static/css/default.css create mode 100644 clatoolkit_project/dataintegration/plugins/wordpress/templates/config.html diff --git a/clatoolkit_project/clatoolkit/models.py b/clatoolkit_project/clatoolkit/models.py index 5e0c353..5b2f24a 100644 --- a/clatoolkit_project/clatoolkit/models.py +++ b/clatoolkit_project/clatoolkit/models.py @@ -3,7 +3,8 @@ from django_pgjson.fields import JsonBField from django.core.exceptions import ValidationError from django_pgjson.fields import JsonField -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, SuspiciousOperation +from django.http import Http404 from django.utils import timezone import os @@ -224,6 +225,18 @@ def get_required_platforms(self): return platforms + @classmethod + def from_get(cls, request): + try: + unit_id = request.GET["unit"] + except: + raise SuspiciousOperation("Unit not specified") + try: + unit = cls.objects.get(id=unit_id) + return unit + except cls.DoesNotExist: + raise Http404 + class UnitOfferingMembership(models.Model): user = models.ForeignKey(User) diff --git a/clatoolkit_project/clatoolkit_project/settings.py b/clatoolkit_project/clatoolkit_project/settings.py index d41f120..bbb8169 100644 --- a/clatoolkit_project/clatoolkit_project/settings.py +++ b/clatoolkit_project/clatoolkit_project/settings.py @@ -12,6 +12,7 @@ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os +import inspect BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -132,31 +133,6 @@ USE_TZ = True - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.8/howto/static-files/ - -# web accessible folder -STATIC_ROOT = BASE_DIR #os.path.join(BASE_DIR, 'static') - -# URL prefix for static files. -STATIC_URL = '/static/' - -STATIC_PATH = os.path.join(BASE_DIR,'static') - -# Additional locations of static files -STATICFILES_DIRS = ( - # location of your application, should not be public web accessible - STATIC_PATH, -) - -# List of finder classes that know how to find static files in -# various locations. -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -) - AUTH_PROFILE_MODULE = "account.userprofile" GA_TRACKING_ID = '' @@ -200,3 +176,32 @@ }, }, ] + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.8/howto/static-files/ + +# web accessible folder +STATIC_ROOT = BASE_DIR #os.path.join(BASE_DIR, 'static') + +# URL prefix for static files. +STATIC_URL = '/static/' + +STATIC_PATH = os.path.join(BASE_DIR,'static') + +# Additional locations of static files +STATICFILES_DIRS = [ + # location of your application, should not be public web accessible + STATIC_PATH, +] + +# Include the static dir for plugins so they can define their own assets +for plugin in pluginModules: + dir = os.path.join(PLUGIN_PATH, plugin, "static") + STATICFILES_DIRS.append((plugin, dir),) + +# List of finder classes that know how to find static files in +# various locations. +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', +) diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py index fe41f7f..afb1b9e 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py +++ b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py @@ -1,4 +1,4 @@ -from django.http import HttpResponse, HttpResponseRedirect, Http404 +from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponseBadRequest from django.shortcuts import render from django.conf.urls import url from django.core.exceptions import PermissionDenied @@ -22,6 +22,29 @@ class WordPressPlugin(DIBasePlugin, DIPluginDashboardMixin): user_api_association_name = "WP username" + @classmethod + def config_view(cls, request): + unit = UnitOffering.from_get(request) + if UnitOfferingMembership.is_admin(request.user, unit): + instances = cls.get_intances(unit) + + return render(request, "wordpress/templates/config.html", {"unit": unit, "instances": instances}) + else: + raise PermissionDenied + + @classmethod + def delete_instance_view(cls, request): + unit = UnitOffering.from_get(request) + if UnitOfferingMembership.is_admin(request.user, unit): + try: + instance = request.GET["instance"] + except: + return HttpResponseBadRequest("Instance not specified") + cls.delete_instance(unit, instance) + return HttpResponseRedirect(reverse("{}_config".format(cls.platform)) + "?unit={}".format(unit.id)) + else: + raise PermissionDenied + @classmethod def connect_view(cls, request): if request.method == "POST": @@ -43,7 +66,7 @@ def connect_view(cls, request): # Setup URLs for use in the handshake process request_token_url = "{}/oauth1/request".format(wp_root) base_authorization_url = "{}/oauth1/authorize".format(wp_root) - callback_path = reverse("{}-authorize".format(cls.platform)) + callback_path = reverse("{}_authorize".format(cls.platform)) callback_uri = "{}://{}{}".format(request.scheme, request.get_host(), callback_path) # Start the oauth flow and get temporary credentials @@ -63,10 +86,10 @@ def connect_view(cls, request): return HttpResponseRedirect(authorization_url) else: - unit_id = request.GET["unit"] + unit = UnitOffering.from_get(request) form = ConnectForm() - return render(request, "wordpress/templates/connect.html", {"form": form, "unit_id":unit_id}) + return render(request, "wordpress/templates/connect.html", {"form": form, "unit": unit}) @classmethod def authorize_view(cls, request): @@ -85,9 +108,11 @@ def refresh_view(cls, request): def get_url_patterns(cls): """Returns the URL patterns used by the plugin""" return [ - url(r'^connect/$', login_required(cls.connect_view), name="{}-connect".format(CLRecipe.PLATFORM_WORDPRESS)), - url(r'^authorize/$', login_required(cls.authorize_view), name="{}-authorize".format(CLRecipe.PLATFORM_WORDPRESS)), - url(r'^refresh/$', login_required(cls.refresh_view), name="{}-refresh".format(CLRecipe.PLATFORM_WORDPRESS)), + url(r'^config/$', login_required(cls.config_view), name="{}_config".format(CLRecipe.PLATFORM_WORDPRESS)), + url(r'^connect/$', login_required(cls.connect_view), name="{}_connect".format(CLRecipe.PLATFORM_WORDPRESS)), + url(r'^delete/$', login_required(cls.delete_instance_view), name="{}_delete_instance".format(CLRecipe.PLATFORM_WORDPRESS)), + url(r'^authorize/$', login_required(cls.authorize_view), name="{}_authorize".format(CLRecipe.PLATFORM_WORDPRESS)), + url(r'^refresh/$', login_required(cls.refresh_view), name="{}_refresh".format(CLRecipe.PLATFORM_WORDPRESS)), ] def __init__(self): @@ -117,7 +142,7 @@ def authorize(cls, request): cls.save_access_token(unit, wp_root, oauth_tokens.get('oauth_token'), oauth_tokens.get('oauth_token_secret')) - return HttpResponseRedirect("/") + return HttpResponseRedirect(reverse("{}_config".format(cls.platform)) + "?unit={}".format(unit.id)) else: raise PermissionDenied @@ -142,6 +167,16 @@ def add_instance(cls, unit, wp_root, access_key, access_secret): pc = PlatformConfig(unit=unit, platform=cls.platform, config=config) pc.save() + @classmethod + def delete_instance(cls, unit, wp_root): + try: + pc = PlatformConfig.objects.get(unit=unit, platform=cls.platform) + pc.config.pop(wp_root) + pc.save() + # If no existing config exists + except PlatformConfig.DoesNotExist: + pass + @classmethod def get_platform_config(cls, unit): return PlatformConfig.objects.get(unit=unit, platform=cls.platform).config @@ -150,6 +185,12 @@ def get_platform_config(cls, unit): def get_instance_config(cls, unit, wp_root): return PlatformConfig.objects.get(unit=unit, platform=cls.platform).config[wp_root] + @classmethod + def get_intances(cls, unit): + config = cls.get_platform_config(unit) + sites = [s for s in config] + return sites + @classmethod def get_client_key(cls, unit, wp_root): config = cls.get_instance_config(unit, wp_root) diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/forms.py b/clatoolkit_project/dataintegration/plugins/wordpress/forms.py index a36828f..bf538b4 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/forms.py +++ b/clatoolkit_project/dataintegration/plugins/wordpress/forms.py @@ -2,7 +2,7 @@ class ConnectForm(forms.Form): - wp_root = forms.URLField(label="WordPress Root URL") - client_key = forms.CharField() - client_secret = forms.CharField() + wp_root = forms.URLField(label="WordPress Root URL", widget=forms.TextInput(attrs={'class': 'form-control'})) + client_key = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control'})) + client_secret = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control'})) unit_id = forms.CharField(widget=forms.HiddenInput()) diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/static/css/default.css b/clatoolkit_project/dataintegration/plugins/wordpress/static/css/default.css new file mode 100644 index 0000000..668f378 --- /dev/null +++ b/clatoolkit_project/dataintegration/plugins/wordpress/static/css/default.css @@ -0,0 +1,12 @@ +body { + margin-top: 20px; +} + +h4 { + margin-bottom: 0; +} + +p.btn-line-height { + line-height: 34px; + margin: 0; +} \ No newline at end of file diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/templates/config.html b/clatoolkit_project/dataintegration/plugins/wordpress/templates/config.html new file mode 100644 index 0000000..aaa4932 --- /dev/null +++ b/clatoolkit_project/dataintegration/plugins/wordpress/templates/config.html @@ -0,0 +1,43 @@ + + + + + WordPress Config + + + + + + + + +
+
+
+
+
+

WordPress Configuration for {{ unit.code }} {{ unit.name }}

+
+
+

Connected WordPress Instances

+
+
+ {% for instance in instances %} +
+

{{ instance }}

+
+ Delete +
+
+ {% endfor %} + +
+
+
+
+
+ + \ No newline at end of file diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/templates/connect.html b/clatoolkit_project/dataintegration/plugins/wordpress/templates/connect.html index 25ba03d..2073846 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/templates/connect.html +++ b/clatoolkit_project/dataintegration/plugins/wordpress/templates/connect.html @@ -1,14 +1,47 @@ -
- {% csrf_token %} - {{ form.wp_root.label_tag }} - {{ form.wp_root }} -
- {{ form.client_key.label_tag }} - {{ form.client_key }} -
- {{ form.client_secret.label_tag }} - {{ form.client_secret }} -
- - -
\ No newline at end of file + + + + + WordPress Config + + + + + + + + +
+
+
+
+
+

Add new WordPress instance to {{ unit.code }} {{ unit.name }}

+
+
+
+ {% csrf_token %} + +
+ {{ form.wp_root.label_tag }} + {{ form.wp_root }} +
+
+ {{ form.client_key.label_tag }} + {{ form.client_key }} +
+
+ {{ form.client_secret.label_tag }} + {{ form.client_secret }} +
+
+ +
+
+
+
+
+
+
+ + \ No newline at end of file From da481539193ae539b913550ddce24fe1b7ef4647 Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Fri, 28 Oct 2016 10:41:07 +1100 Subject: [PATCH 12/13] Plugins connected with a PlatformConfig are shown on the myunits page --- clatoolkit_project/dashboard/views.py | 17 +++++++++++++++++ .../plugins/wordpress/cladi_plugin.py | 11 +++++++++++ .../templates/dashboard/myunits.html | 19 ++++++++++++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/clatoolkit_project/dashboard/views.py b/clatoolkit_project/dashboard/views.py index 27d202f..22dde79 100644 --- a/clatoolkit_project/dashboard/views.py +++ b/clatoolkit_project/dashboard/views.py @@ -1,10 +1,12 @@ from django.shortcuts import render from django.shortcuts import render_to_response +from django.core.urlresolvers import reverse from django.template import RequestContext from django.http import HttpResponse from django.db import connection from utils import * from clatoolkit.models import OfflinePlatformAuthToken, UserProfile, OauthFlowTemp, UnitOffering, UnitOfferingMembership, DashboardReflection, LearningRecord, Classification, UserClassification, GroupMap, UserTrelloCourseBoardMap +from dataintegration.models import PlatformConfig from django.contrib.auth.decorators import login_required from django.contrib.auth import logout from functools import wraps @@ -177,6 +179,21 @@ def myunits(request): # Get a users memberships to unit offerings memberships = UnitOfferingMembership.objects.filter(user=request.user, unit__enabled=True).select_related('unit') + # Get Plugin details + for i in range(0, len(memberships)): + platform_configs = memberships[i].unit.platformconfig_set.all() + if len(platform_configs) > 0: + memberships[i].plugins = [] + for config in platform_configs: + platform = config.platform + groups = settings.DATAINTEGRATION_PLUGINS[platform].get_groups(memberships[i].unit) + memberships[i].plugins.append({ + "label": platform, + "platform": platform, + "refresh": reverse("{}_refresh".format(platform)), + "groups": groups + }) + role = request.user.userprofile.role show_dashboardnav = False diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py index afb1b9e..d5c1444 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py +++ b/clatoolkit_project/dataintegration/plugins/wordpress/cladi_plugin.py @@ -191,6 +191,17 @@ def get_intances(cls, unit): sites = [s for s in config] return sites + @classmethod + def get_groups(cls, unit): + instances = cls.get_intances(unit) + result = [] + for instance in instances: + result.append({ + "label": instance, + "a": instance + }) + return result + @classmethod def get_client_key(cls, unit, wp_root): config = cls.get_instance_config(unit, wp_root) diff --git a/clatoolkit_project/templates/dashboard/myunits.html b/clatoolkit_project/templates/dashboard/myunits.html index 54be5e6..2aabe14 100644 --- a/clatoolkit_project/templates/dashboard/myunits.html +++ b/clatoolkit_project/templates/dashboard/myunits.html @@ -97,7 +97,6 @@ Refresh Imported Data {% endif %} -
{% if membership.unit.github_urls_as_list|length > 0 %} GitHub URLs: {% for url in membership.unit.github_urls_as_list %} {{ url }} {% endfor %} [ @@ -117,6 +116,24 @@ Imported Data
{% endif %} + {% if membership.plugins %} + {% for plugin in membership.plugins %} + {{ plugin.label }} + {% for group in plugin.groups %} + {% if group.a %} + {{ group.label }} + {% else %} +

{{ group.label }}

+ {% endif %} + {% endfor %} + [ + Activity | + Content Analysis | + Social Network Analysis + ] + Refresh Imported Data + {% endfor %} + {% endif %} {% if unit.enable_coi_classifier %}
From dc394ab2fa0a0dea370670230793774e5f7f98ac Mon Sep 17 00:00:00 2001 From: Tommaso Armstrong Date: Fri, 28 Oct 2016 11:12:53 +1100 Subject: [PATCH 13/13] Links to configure plugins in update unit offering page --- clatoolkit_project/clatoolkit/views.py | 14 +++++++++++++- .../plugins/wordpress/templates/config.html | 2 +- .../templates/clatoolkit/createoffering.html | 7 ++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/clatoolkit_project/clatoolkit/views.py b/clatoolkit_project/clatoolkit/views.py index 1658c7d..a0cd001 100644 --- a/clatoolkit_project/clatoolkit/views.py +++ b/clatoolkit_project/clatoolkit/views.py @@ -1,5 +1,6 @@ from django.shortcuts import render, render_to_response from django.shortcuts import redirect +from django.core.urlresolvers import reverse from django.contrib.auth import authenticate, login from django.http import HttpResponseRedirect, HttpResponse, Http404 @@ -405,7 +406,18 @@ def update_offering(request, unit_id): else: form = CreateOfferingForm(instance=unit) - return render(request, "clatoolkit/createoffering.html", {'verb': 'Update', 'form': form}) + all_plugins = settings.DATAINTEGRATION_PLUGINS + excluded = ["Blog", "Diigo", "Forum", "GitHub", "Twitter", "YouTube", "facebook", "trello"] + + plugins = [] + for plugin in all_plugins: + if plugin not in excluded: + plugins.append({ + "a": reverse("{}_config".format(plugin)), + "label": "Configure {}".format(plugin) + }) + + return render(request, "clatoolkit/createoffering.html", {'verb': 'Update', 'form': form, 'unit': unit, "plugins":plugins}) else: raise PermissionDenied() diff --git a/clatoolkit_project/dataintegration/plugins/wordpress/templates/config.html b/clatoolkit_project/dataintegration/plugins/wordpress/templates/config.html index aaa4932..1e95edc 100644 --- a/clatoolkit_project/dataintegration/plugins/wordpress/templates/config.html +++ b/clatoolkit_project/dataintegration/plugins/wordpress/templates/config.html @@ -31,7 +31,7 @@

Connected WordPress Instances

{% endfor %} diff --git a/clatoolkit_project/templates/clatoolkit/createoffering.html b/clatoolkit_project/templates/clatoolkit/createoffering.html index d6655a6..f727e69 100644 --- a/clatoolkit_project/templates/clatoolkit/createoffering.html +++ b/clatoolkit_project/templates/clatoolkit/createoffering.html @@ -38,7 +38,12 @@ {% endif %} {% endfor %} - + {% if verb == "Update" %} + {% for plugin in plugins %} + {{ plugin.label }} + {% endfor %} + {% endif %} +